From 048e7b364a252d51963a7c76239ae18b1f756761 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 10:00:59 -0400 Subject: [PATCH 001/447] Add Holiday class, which represents a holiday on a given date --- workalendar/core.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/workalendar/core.py b/workalendar/core.py index 5cc1d253..8db40a6d 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -11,6 +11,7 @@ from dateutil import easter from lunardate import LunarDate from calverter import Calverter +from dateutil import relativedelta as rd MON, TUE, WED, THU, FRI, SAT, SUN = range(7) @@ -500,3 +501,36 @@ def get_islamic_holidays(self): class JalaliMixin(CalverterMixin): conversion_method = 'jalali' + + +class Holiday(datetime.date): + """ + A named holiday with a name, a textual indicated date, the indicated date, + and a weekend hint (used to calculate an observed date). For example, to + create a holiday for New Year's Day, but have it observed on Monday if it + falls on a weekend: + + >>> nyd = Holiday("New year", "First day in January", date(2014, 1, 1)) + + But if New Year's Eve is also a holiday, and it too falls on a weekend, + many calendars will have that holiday fall back to the previous friday: + + >>> nye = Holiday("New year's eve", "Last day of the year", date(2014, 12, 31), rd.FR(-1)) + """ + def __new__(cls, name, indication, date, weekend_hint=rd.MO(1)): + return datetime.date.__new__(cls, date.year, date.month, date.day) + + def __init__(self, name, indication, date, weekend_hint=rd.MO(1)): + self.name = name + self.indication = indication + self.weekend_hint = weekend_hint + + @property + def observed(self): + """ + The date this holiday is observed. If the holiday occurs on a weekend, + it is normally observed on the following Monday. + The weekend hint might be rd.FR(-1), meaning the previous Friday. + """ + delta = rd.relativedelta(weekday=self.weekend_hint) + return self + delta if self.weekday() > 4 else self From 00cb7f91503058896eba148d8aef337d7fb03800 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 10:07:06 -0400 Subject: [PATCH 002/447] Add replace method to allow year to be replaced --- workalendar/core.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/workalendar/core.py b/workalendar/core.py index 8db40a6d..fbccbe16 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -525,6 +525,11 @@ def __init__(self, name, indication, date, weekend_hint=rd.MO(1)): self.indication = indication self.weekend_hint = weekend_hint + def replace(self, *args, **kwargs): + replaced = self.replace(*args, **kwargs) + replaced.indication = self.indication + replaced.weekend_hint = self.weekend_hint + @property def observed(self): """ From ef903a7dded7e38bd679bc523564da68758e32b6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 10:12:22 -0400 Subject: [PATCH 003/447] Load FIXED_HOLIDAYS as Holiday objects --HG-- extra : amend_source : e2b1f36319875798bf21deb3075deab65f9ea230 --- workalendar/core.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index fbccbe16..db3bf03d 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -26,10 +26,18 @@ def __init__(self): def get_fixed_holidays(self, year): """Return the fixed days according to the FIXED_HOLIDAYS class property """ - days = [] - for month, day, label in self.FIXED_HOLIDAYS: - days.append((date(year, month, day), label)) - return days + fixed_holidays = self._load_fixed_holidays() + return [day.replace(year=year) for day in fixed_holidays] + + @classmethod + def _load_fixed_holidays(cls): + """For backward compatibility, load Holiday objects from tuples in + FIXED_HOLIDAYS class property. + """ + return ( + Holiday(label, None, date(2000, month, day)) + for month, day, label in cls.FIXED_HOLIDAYS + ) def get_variable_days(self, year): return [] From a9fd8452206959af0d718beedfbd6373b90d4718 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 10:33:00 -0400 Subject: [PATCH 004/447] Fix references to 'date' class and use super for invoking the constructor. --- workalendar/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index db3bf03d..59f4944f 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -511,7 +511,7 @@ class JalaliMixin(CalverterMixin): conversion_method = 'jalali' -class Holiday(datetime.date): +class Holiday(date): """ A named holiday with a name, a textual indicated date, the indicated date, and a weekend hint (used to calculate an observed date). For example, to @@ -526,7 +526,7 @@ class Holiday(datetime.date): >>> nye = Holiday("New year's eve", "Last day of the year", date(2014, 12, 31), rd.FR(-1)) """ def __new__(cls, name, indication, date, weekend_hint=rd.MO(1)): - return datetime.date.__new__(cls, date.year, date.month, date.day) + return super(Holiday, cls).__new__(cls, date.year, date.month, date.day) def __init__(self, name, indication, date, weekend_hint=rd.MO(1)): self.name = name From c5b82c3c9c16aaa8eb2d6499d6e41d146d135687 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 10:30:51 -0400 Subject: [PATCH 005/447] Added two-tuple compatibility to Holiday object --HG-- extra : rebase_source : d0ec2a68e9565aaa015984de775b07df1d263189 --- workalendar/core.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/workalendar/core.py b/workalendar/core.py index 59f4944f..0c6e76a2 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -524,6 +524,15 @@ class Holiday(date): many calendars will have that holiday fall back to the previous friday: >>> nye = Holiday("New year's eve", "Last day of the year", date(2014, 12, 31), rd.FR(-1)) + + For compatibility, a Holiday may be treated like a tuple of (label, date) + + >>> nyd[0] + 'New year' + >>> nyd[1] == date(2014, 1, 1) + True + >>> label, d = nyd + """ def __new__(cls, name, indication, date, weekend_hint=rd.MO(1)): return super(Holiday, cls).__new__(cls, date.year, date.month, date.day) @@ -533,6 +542,20 @@ def __init__(self, name, indication, date, weekend_hint=rd.MO(1)): self.indication = indication self.weekend_hint = weekend_hint + def __getitem__(self, n): + """ + for compatibility as a two-tuple + """ + tp = self.name, self + return tp[n] + + def __iter__(self): + """ + for compatibility as a two-tuple + """ + tp = self.name, self + return iter(tp) + def replace(self, *args, **kwargs): replaced = self.replace(*args, **kwargs) replaced.indication = self.indication From 7c744bba9eacf4c0c3240ef6d0e3bb0bc9fc3bce Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 10:44:07 -0400 Subject: [PATCH 006/447] Moved holiday to the top and defined 'New year' as a Holiday. --- workalendar/core.py | 124 ++++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index 0c6e76a2..bcf603ce 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -16,6 +16,67 @@ MON, TUE, WED, THU, FRI, SAT, SUN = range(7) +class Holiday(date): + """ + A named holiday with a name, a textual indicated date, the indicated date, + and a weekend hint (used to calculate an observed date). For example, to + create a holiday for New Year's Day, but have it observed on Monday if it + falls on a weekend: + + >>> nyd = Holiday("New year", "First day in January", date(2014, 1, 1)) + + But if New Year's Eve is also a holiday, and it too falls on a weekend, + many calendars will have that holiday fall back to the previous friday: + + >>> nye = Holiday("New year's eve", "Last day of the year", date(2014, 12, 31), rd.FR(-1)) + + For compatibility, a Holiday may be treated like a tuple of (label, date) + + >>> nyd[0] + 'New year' + >>> nyd[1] == date(2014, 1, 1) + True + >>> label, d = nyd + + """ + def __new__(cls, name, indication, date, weekend_hint=rd.MO(1)): + return super(Holiday, cls).__new__(cls, date.year, date.month, date.day) + + def __init__(self, name, indication, date, weekend_hint=rd.MO(1)): + self.name = name + self.indication = indication + self.weekend_hint = weekend_hint + + def __getitem__(self, n): + """ + for compatibility as a two-tuple + """ + tp = self.name, self + return tp[n] + + def __iter__(self): + """ + for compatibility as a two-tuple + """ + tp = self.name, self + return iter(tp) + + def replace(self, *args, **kwargs): + replaced = self.replace(*args, **kwargs) + replaced.indication = self.indication + replaced.weekend_hint = self.weekend_hint + + @property + def observed(self): + """ + The date this holiday is observed. If the holiday occurs on a weekend, + it is normally observed on the following Monday. + The weekend hint might be rd.FR(-1), meaning the previous Friday. + """ + delta = rd.relativedelta(weekday=self.weekend_hint) + return self + delta if self.weekday() > 4 else self + + class Calendar(object): FIXED_HOLIDAYS = () @@ -323,7 +384,7 @@ class WesternCalendar(Calendar): shift_new_years_day = False FIXED_HOLIDAYS = ( - (1, 1, 'New year'), + Holiday('New year', 'First day in January', date(2000, 1, 1)), ) def get_weekend_days(self): @@ -509,64 +570,3 @@ def get_islamic_holidays(self): class JalaliMixin(CalverterMixin): conversion_method = 'jalali' - - -class Holiday(date): - """ - A named holiday with a name, a textual indicated date, the indicated date, - and a weekend hint (used to calculate an observed date). For example, to - create a holiday for New Year's Day, but have it observed on Monday if it - falls on a weekend: - - >>> nyd = Holiday("New year", "First day in January", date(2014, 1, 1)) - - But if New Year's Eve is also a holiday, and it too falls on a weekend, - many calendars will have that holiday fall back to the previous friday: - - >>> nye = Holiday("New year's eve", "Last day of the year", date(2014, 12, 31), rd.FR(-1)) - - For compatibility, a Holiday may be treated like a tuple of (label, date) - - >>> nyd[0] - 'New year' - >>> nyd[1] == date(2014, 1, 1) - True - >>> label, d = nyd - - """ - def __new__(cls, name, indication, date, weekend_hint=rd.MO(1)): - return super(Holiday, cls).__new__(cls, date.year, date.month, date.day) - - def __init__(self, name, indication, date, weekend_hint=rd.MO(1)): - self.name = name - self.indication = indication - self.weekend_hint = weekend_hint - - def __getitem__(self, n): - """ - for compatibility as a two-tuple - """ - tp = self.name, self - return tp[n] - - def __iter__(self): - """ - for compatibility as a two-tuple - """ - tp = self.name, self - return iter(tp) - - def replace(self, *args, **kwargs): - replaced = self.replace(*args, **kwargs) - replaced.indication = self.indication - replaced.weekend_hint = self.weekend_hint - - @property - def observed(self): - """ - The date this holiday is observed. If the holiday occurs on a weekend, - it is normally observed on the following Monday. - The weekend hint might be rd.FR(-1), meaning the previous Friday. - """ - delta = rd.relativedelta(weekday=self.weekend_hint) - return self + delta if self.weekday() > 4 else self From 85591a9a5c8c0feb1c788bf2cf1a1daf9bd646e2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 11:05:28 -0400 Subject: [PATCH 007/447] Moved fixed definition construction to Holiday class. Now allow FIXED_HOLIDAYS to contain Holiday entries. --- workalendar/core.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index bcf603ce..64fd0a47 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -76,6 +76,17 @@ def observed(self): delta = rd.relativedelta(weekday=self.weekend_hint) return self + delta if self.weekday() > 4 else self + @classmethod + def _from_fixed_definition(cls, item): + """For backward compatibility, load Holiday object from an item of + FIXED_HOLIDAYS class property, which might be just a tuple of + month, day, label. + """ + if isinstance(item, tuple): + month, day, label = item + item = Holiday(label, None, date(2000, month, day)) + return item + class Calendar(object): @@ -87,19 +98,10 @@ def __init__(self): def get_fixed_holidays(self, year): """Return the fixed days according to the FIXED_HOLIDAYS class property """ - fixed_holidays = self._load_fixed_holidays() + fixed_holidays = map(Holiday._from_fixed_definition, + self.FIXED_HOLIDAYS) return [day.replace(year=year) for day in fixed_holidays] - @classmethod - def _load_fixed_holidays(cls): - """For backward compatibility, load Holiday objects from tuples in - FIXED_HOLIDAYS class property. - """ - return ( - Holiday(label, None, date(2000, month, day)) - for month, day, label in cls.FIXED_HOLIDAYS - ) - def get_variable_days(self, year): return [] From 6d7c8393e28c2d15a38b7e20911f125a1cfb8511 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 11:07:45 -0400 Subject: [PATCH 008/447] Update United States fixed holidays to use Holiday definitions. --- workalendar/america.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workalendar/america.py b/workalendar/america.py index af576fc4..4661ea99 100644 --- a/workalendar/america.py +++ b/workalendar/america.py @@ -3,15 +3,15 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) from datetime import date, timedelta -from workalendar.core import WesternCalendar, ChristianMixin +from workalendar.core import WesternCalendar, ChristianMixin, Holiday from workalendar.core import SUN, MON, TUE, WED, THU, FRI, SAT class UnitedStates(WesternCalendar, ChristianMixin): "United States of America" FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( - (7, 4, 'Independence Day'), - (11, 11, 'Veterans Day'), + Holiday('Independence Day', 'July 4', date(2000, 7, 4)), + Holiday('Veterans Day', 'Nov 11', date(2000, 11, 11)), ) @staticmethod From 290e89feddb7af2e70afb93fb62a13218fdd4a7e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 11:20:41 -0400 Subject: [PATCH 009/447] Update UnitedStates variable days with Holiday instances --HG-- extra : amend_source : f15d243f1146ebf29f8970f2e3159be9265df632 --- workalendar/america.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/workalendar/america.py b/workalendar/america.py index 4661ea99..183ed8f1 100644 --- a/workalendar/america.py +++ b/workalendar/america.py @@ -3,8 +3,9 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) from datetime import date, timedelta +from dateutil import relativedelta as rd from workalendar.core import WesternCalendar, ChristianMixin, Holiday -from workalendar.core import SUN, MON, TUE, WED, THU, FRI, SAT +from workalendar.core import SUN, MON, TUE, WED, FRI, SAT class UnitedStates(WesternCalendar, ChristianMixin): @@ -22,30 +23,35 @@ def get_variable_days(self, year): # usual variable days days = super(UnitedStates, self).get_variable_days(year) days += [ - (UnitedStates.get_nth_weekday_in_month(year, 1, MON, 3), - 'Martin Luther King, Jr. Day'), + Holiday("Martin Luther King, Jr. Day", "3rd Monday in January", + date(year, 1, 1) + rd.relativedelta(weekday=rd.MO(3))), - (UnitedStates.get_nth_weekday_in_month(year, 2, MON, 3), - "Washington's Birthday"), + Holiday("Washington's Birthday", "3rd Monday in February", + date(year, 2, 1) + rd.relativedelta(weekday=rd.MO(3))), - (UnitedStates.get_last_weekday_in_month(year, 5, MON), - "Memorial Day"), + Holiday("Memorial Day", "Last Monday in May", + date(year, 5, 31) + rd.relativedelta(weekday=rd.MO(-1))), - (UnitedStates.get_nth_weekday_in_month(year, 9, MON), - "Labor Day"), + Holiday("Labor Day", "1st Monday in September", + date(year, 9, 1) + rd.relativedelta(weekday=rd.MO(1))), - (UnitedStates.get_nth_weekday_in_month(year, 10, MON, 2), - "Colombus Day"), + Holiday("Colombus Day", "2nd Monday in October", + date(year, 10, 1) + rd.relativedelta(weekday=rd.MO(2))), - (UnitedStates.get_nth_weekday_in_month(year, 11, THU, 4), - "Thanksgiving Day"), + Holiday("Thanksgiving Day", "4th Thursday in November", + date(year, 11, 1) + rd.relativedelta(weekday=rd.TH(4))), ] # Inauguration day if UnitedStates.is_presidential_year(year - 1): inauguration_day = date(year, 1, 20) if inauguration_day.weekday() == SUN: inauguration_day = date(year, 1, 21) - days.append((inauguration_day, "Inauguration Day")) + h = Holiday( + "Inauguration Day", + "January 20 (or 21st if Sunday) following an election year", + inauguration_day, + ) + days.append(h) return days From e316e6b70bba7a3edab5bfe7155e1163b79aafcf Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 11:25:42 -0400 Subject: [PATCH 010/447] Fix flaws in Holiday.replace --- workalendar/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workalendar/core.py b/workalendar/core.py index 64fd0a47..45162a39 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -62,9 +62,10 @@ def __iter__(self): return iter(tp) def replace(self, *args, **kwargs): - replaced = self.replace(*args, **kwargs) + replaced = super(Holiday, self).replace(*args, **kwargs) replaced.indication = self.indication replaced.weekend_hint = self.weekend_hint + return replaced @property def observed(self): From aba2747456a6383fdccf003d21d1fc36b2a16432 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 11:30:46 -0400 Subject: [PATCH 011/447] Must copy name also --- workalendar/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/workalendar/core.py b/workalendar/core.py index 45162a39..5faa0958 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -63,6 +63,7 @@ def __iter__(self): def replace(self, *args, **kwargs): replaced = super(Holiday, self).replace(*args, **kwargs) + replaced.name = self.name replaced.indication = self.indication replaced.weekend_hint = self.weekend_hint return replaced From 1ece00983c47af6ed0bc75cb3c7775d7d879f97f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 11:32:00 -0400 Subject: [PATCH 012/447] Correct two-tuple compatibility --- workalendar/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index 5faa0958..3d44ff8f 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -51,14 +51,14 @@ def __getitem__(self, n): """ for compatibility as a two-tuple """ - tp = self.name, self + tp = self, self.name return tp[n] def __iter__(self): """ for compatibility as a two-tuple """ - tp = self.name, self + tp = self, self.name return iter(tp) def replace(self, *args, **kwargs): From ae0431318072f5cc1068607dc7a4f5e00ca8b517 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 11:34:38 -0400 Subject: [PATCH 013/447] Construct Holiday instances from variable holidays as well. --- workalendar/core.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/workalendar/core.py b/workalendar/core.py index 3d44ff8f..28dc73fd 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -89,6 +89,16 @@ def _from_fixed_definition(cls, item): item = Holiday(label, None, date(2000, month, day)) return item + @classmethod + def _from_resolved_definition(cls, item): + """For backward compatibility, load Holiday object from a two-tuple + or existing Holiday instance. + """ + if isinstance(item, tuple): + date, label = item + item = Holiday(label, None, date) + return item + class Calendar(object): @@ -123,7 +133,9 @@ def holidays(self, year=None): return self._holidays[year] # Here we process the holiday specific calendar - temp_calendar = tuple(self.get_calendar_holidays(year)) + days = self.get_calendar_holidays(year) + days = map(Holiday._from_resolved_definition, days) + temp_calendar = tuple(days) # it is sorted self._holidays[year] = sorted(temp_calendar) From 6d9d3b9d2df452223e50a75c0ce3cd1f57633d19 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 11:47:38 -0400 Subject: [PATCH 014/447] Add support for addition of a Holiday --- workalendar/core.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/workalendar/core.py b/workalendar/core.py index 28dc73fd..f6ba99fa 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -68,6 +68,11 @@ def replace(self, *args, **kwargs): replaced.weekend_hint = self.weekend_hint return replaced + def __add__(self, delta): + sum = super(Holiday, self).__add__(delta) + return self.__class__(self.name, self.indication, sum, + self.weekend_hint) + @property def observed(self): """ From 0b74222a81942fc34806217e19f4884633e6af8c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 11:55:49 -0400 Subject: [PATCH 015/447] South Africa now uses get_fixed_holidays, simplifying the implementation. --- workalendar/africa.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/workalendar/africa.py b/workalendar/africa.py index 38fa416c..2c984e17 100644 --- a/workalendar/africa.py +++ b/workalendar/africa.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (absolute_import, division, print_function, unicode_literals) -from datetime import timedelta, date +from datetime import timedelta from workalendar.core import WesternCalendar from workalendar.core import SUN from workalendar.core import IslamicMixin, ChristianMixin @@ -116,11 +116,9 @@ def get_variable_days(self, year): days = super(SouthAfrica, self).get_variable_days(year) days.append(self.get_family_day(year)) # compute shifting days - for month, day, label in self.FIXED_HOLIDAYS: - holiday = date(year, month, day) + for holiday in self.get_fixed_holidays(year): if holiday.weekday() == SUN: - days.append(( - holiday + timedelta(days=1), - "%s substitute" % label - )) + sub = holiday + timedelta(days=1) + sub.name = sub.name + ' substitute' + days.append(sub) return days From 4e3089f2342badcfad1d81da3dcf7e5671e428c9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 12:12:13 -0400 Subject: [PATCH 016/447] Correct flake warnings that I don't get --- workalendar/america.py | 18 ++++++++++++------ workalendar/core.py | 16 ++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/workalendar/america.py b/workalendar/america.py index 183ed8f1..8cc81b6e 100644 --- a/workalendar/america.py +++ b/workalendar/america.py @@ -23,22 +23,28 @@ def get_variable_days(self, year): # usual variable days days = super(UnitedStates, self).get_variable_days(year) days += [ - Holiday("Martin Luther King, Jr. Day", "3rd Monday in January", + Holiday( + "Martin Luther King, Jr. Day", "3rd Monday in January", date(year, 1, 1) + rd.relativedelta(weekday=rd.MO(3))), - Holiday("Washington's Birthday", "3rd Monday in February", + Holiday( + "Washington's Birthday", "3rd Monday in February", date(year, 2, 1) + rd.relativedelta(weekday=rd.MO(3))), - Holiday("Memorial Day", "Last Monday in May", + Holiday( + "Memorial Day", "Last Monday in May", date(year, 5, 31) + rd.relativedelta(weekday=rd.MO(-1))), - Holiday("Labor Day", "1st Monday in September", + Holiday( + "Labor Day", "1st Monday in September", date(year, 9, 1) + rd.relativedelta(weekday=rd.MO(1))), - Holiday("Colombus Day", "2nd Monday in October", + Holiday( + "Colombus Day", "2nd Monday in October", date(year, 10, 1) + rd.relativedelta(weekday=rd.MO(2))), - Holiday("Thanksgiving Day", "4th Thursday in November", + Holiday( + "Thanksgiving Day", "4th Thursday in November", date(year, 11, 1) + rd.relativedelta(weekday=rd.TH(4))), ] # Inauguration day diff --git a/workalendar/core.py b/workalendar/core.py index f6ba99fa..caa4bb3e 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -28,7 +28,8 @@ class Holiday(date): But if New Year's Eve is also a holiday, and it too falls on a weekend, many calendars will have that holiday fall back to the previous friday: - >>> nye = Holiday("New year's eve", "Last day of the year", date(2014, 12, 31), rd.FR(-1)) + >>> nye = Holiday("New year's eve", "Last day of the year", + ... date(2014, 12, 31), rd.FR(-1)) For compatibility, a Holiday may be treated like a tuple of (label, date) @@ -40,7 +41,8 @@ class Holiday(date): """ def __new__(cls, name, indication, date, weekend_hint=rd.MO(1)): - return super(Holiday, cls).__new__(cls, date.year, date.month, date.day) + return super(Holiday, cls).__new__(cls, date.year, date.month, + date.day) def __init__(self, name, indication, date, weekend_hint=rd.MO(1)): self.name = name @@ -70,8 +72,8 @@ def replace(self, *args, **kwargs): def __add__(self, delta): sum = super(Holiday, self).__add__(delta) - return self.__class__(self.name, self.indication, sum, - self.weekend_hint) + return self.__class__( + self.name, self.indication, sum, self.weekend_hint) @property def observed(self): @@ -115,8 +117,10 @@ def __init__(self): def get_fixed_holidays(self, year): """Return the fixed days according to the FIXED_HOLIDAYS class property """ - fixed_holidays = map(Holiday._from_fixed_definition, - self.FIXED_HOLIDAYS) + fixed_holidays = map( + Holiday._from_fixed_definition, + self.FIXED_HOLIDAYS, + ) return [day.replace(year=year) for day in fixed_holidays] def get_variable_days(self, year): From a0908cc1bd82175bb0b0a1fc668c7bbedb9f9bf5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 12:39:39 -0400 Subject: [PATCH 017/447] One more instance of flake8 --- workalendar/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index caa4bb3e..c5ce9a56 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -41,8 +41,8 @@ class Holiday(date): """ def __new__(cls, name, indication, date, weekend_hint=rd.MO(1)): - return super(Holiday, cls).__new__(cls, date.year, date.month, - date.day) + return super(Holiday, cls).__new__( + cls, date.year, date.month, date.day) def __init__(self, name, indication, date, weekend_hint=rd.MO(1)): self.name = name From 34509e11d085da516a87727c6d8c78fb2ca80ced Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 14:45:34 -0400 Subject: [PATCH 018/447] Use += for clarity --- workalendar/africa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workalendar/africa.py b/workalendar/africa.py index 2c984e17..c2cf7305 100644 --- a/workalendar/africa.py +++ b/workalendar/africa.py @@ -119,6 +119,6 @@ def get_variable_days(self, year): for holiday in self.get_fixed_holidays(year): if holiday.weekday() == SUN: sub = holiday + timedelta(days=1) - sub.name = sub.name + ' substitute' + sub.name += ' substitute' days.append(sub) return days From 4bff4178a26fa14555b9cc1f9941a98418249c46 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 May 2014 22:03:07 -0400 Subject: [PATCH 019/447] Update doctest to match implementation --- workalendar/core.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index c5ce9a56..2d11baba 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -31,13 +31,13 @@ class Holiday(date): >>> nye = Holiday("New year's eve", "Last day of the year", ... date(2014, 12, 31), rd.FR(-1)) - For compatibility, a Holiday may be treated like a tuple of (label, date) + For compatibility, a Holiday may be treated like a tuple of (date, label) - >>> nyd[0] - 'New year' - >>> nyd[1] == date(2014, 1, 1) + >>> nyd[0] == date(2014, 1, 1) True - >>> label, d = nyd + >>> nyd[1] + 'New year' + >>> d, label = nyd """ def __new__(cls, name, indication, date, weekend_hint=rd.MO(1)): From de782e29167cbeba58635b37a7eef08664f5a38d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 08:52:27 -0400 Subject: [PATCH 020/447] Add test capturing the primary use case for the Holiday class - observance dates reflected in each result. --- workalendar/tests/test_core.py | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/workalendar/tests/test_core.py b/workalendar/tests/test_core.py index f0d5a05f..a451a278 100644 --- a/workalendar/tests/test_core.py +++ b/workalendar/tests/test_core.py @@ -1,10 +1,14 @@ from datetime import date from datetime import datetime + +import dateutil.relativedelta as rd + from workalendar.tests import GenericCalendarTest from workalendar.core import MON, TUE, THU, FRI from workalendar.core import Calendar, LunarCalendar from workalendar.core import IslamicMixin, JalaliMixin from workalendar.core import EphemMixin +from workalendar.core import Holiday class CalendarTest(GenericCalendarTest): @@ -201,6 +205,41 @@ def test_datetime(self): self.cal.is_working_day(datetime(2014, 1, 1))) +class SimpleObservanceCalendar(Calendar): + """ + A simple calendar with a couple of holidays with typical observance rules: + If a holiday falls on a weekend, then its observance is shifted to a + nearby weekday. + """ + FIXED_HOLIDAYS = ( + Holiday( + 'Christmas Eve', 'December 24th', date(2000, 12, 24), + weekend_hint=rd.FR(-1), + ), + Holiday('Christmas', 'December 25th', date(2000, 12, 25)), + ) + + +class ObservanceCalendarTest(GenericCalendarTest): + """ + A simple calendar with days shifted for observance. + """ + cal_class = SimpleObservanceCalendar + + def test_observance(self): + """ + Each Holiday returned by the calendar should be aware of its indicated + date and observance date. + """ + holidays = list(self.cal.holidays(2011)) + assert len(holidays) == 2 + xmas_eve, xmas_day = holidays + assert xmas_eve == date(2011, 12, 24) + assert xmas_eve.observed == date(2011, 12, 23) + assert xmas_day == date(2011, 12, 25) + assert xmas_day.observed == date(2011, 12, 26) + + class IslamicMixinTest(GenericCalendarTest): cal_class = IslamicMixin From d6dd3b16a62681553468724e8b41d222e0809f71 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 09:03:28 -0400 Subject: [PATCH 021/447] Only construct a new instance if the add was implemented (adds support for relativedelta addition) --- workalendar/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/workalendar/core.py b/workalendar/core.py index 2d11baba..0f7aa9c2 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -72,6 +72,8 @@ def replace(self, *args, **kwargs): def __add__(self, delta): sum = super(Holiday, self).__add__(delta) + if sum is NotImplemented: + return sum return self.__class__( self.name, self.indication, sum, self.weekend_hint) From 0eb0d23b339bab0e8a15c6b80509a9d7365ae255 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 09:27:48 -0400 Subject: [PATCH 022/447] Switched the order of the arguments, now only requiring 'date' and 'name', similar to the original two-tuple. 'indication' and 'weekend_hint' are now optional arguments as are any additional keyword arguments used in construction. --- workalendar/america.py | 45 ++++++++++++++++++++++------------ workalendar/core.py | 27 ++++++++++---------- workalendar/tests/test_core.py | 4 +-- 3 files changed, 44 insertions(+), 32 deletions(-) diff --git a/workalendar/america.py b/workalendar/america.py index 8cc81b6e..41c5809d 100644 --- a/workalendar/america.py +++ b/workalendar/america.py @@ -11,8 +11,8 @@ class UnitedStates(WesternCalendar, ChristianMixin): "United States of America" FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( - Holiday('Independence Day', 'July 4', date(2000, 7, 4)), - Holiday('Veterans Day', 'Nov 11', date(2000, 11, 11)), + Holiday(date(2000, 7, 4), 'Independence Day', indication='July 4'), + Holiday(date(2000, 11, 11), 'Veterans Day', indication='Nov 11'), ) @staticmethod @@ -24,28 +24,40 @@ def get_variable_days(self, year): days = super(UnitedStates, self).get_variable_days(year) days += [ Holiday( - "Martin Luther King, Jr. Day", "3rd Monday in January", - date(year, 1, 1) + rd.relativedelta(weekday=rd.MO(3))), + date(year, 1, 1) + rd.relativedelta(weekday=rd.MO(3)), + "Martin Luther King, Jr. Day", + indication="3rd Monday in January", + ), Holiday( - "Washington's Birthday", "3rd Monday in February", - date(year, 2, 1) + rd.relativedelta(weekday=rd.MO(3))), + date(year, 2, 1) + rd.relativedelta(weekday=rd.MO(3)), + "Washington's Birthday", + indication="3rd Monday in February", + ), Holiday( - "Memorial Day", "Last Monday in May", - date(year, 5, 31) + rd.relativedelta(weekday=rd.MO(-1))), + date(year, 5, 31) + rd.relativedelta(weekday=rd.MO(-1)), + "Memorial Day", + indication="Last Monday in May", + ), Holiday( - "Labor Day", "1st Monday in September", - date(year, 9, 1) + rd.relativedelta(weekday=rd.MO(1))), + date(year, 9, 1) + rd.relativedelta(weekday=rd.MO(1)), + "Labor Day", + indication="1st Monday in September", + ), Holiday( - "Colombus Day", "2nd Monday in October", - date(year, 10, 1) + rd.relativedelta(weekday=rd.MO(2))), + date(year, 10, 1) + rd.relativedelta(weekday=rd.MO(2)), + "Colombus Day", + indication="2nd Monday in October", + ), Holiday( - "Thanksgiving Day", "4th Thursday in November", - date(year, 11, 1) + rd.relativedelta(weekday=rd.TH(4))), + date(year, 11, 1) + rd.relativedelta(weekday=rd.TH(4)), + "Thanksgiving Day", + indication="4th Thursday in November", + ), ] # Inauguration day if UnitedStates.is_presidential_year(year - 1): @@ -53,9 +65,10 @@ def get_variable_days(self, year): if inauguration_day.weekday() == SUN: inauguration_day = date(year, 1, 21) h = Holiday( - "Inauguration Day", - "January 20 (or 21st if Sunday) following an election year", inauguration_day, + "Inauguration Day", + indication="January 20 (or 21st if Sunday) following an " + "election year", ) days.append(h) return days diff --git a/workalendar/core.py b/workalendar/core.py index 0f7aa9c2..8a899348 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -38,16 +38,17 @@ class Holiday(date): >>> nyd[1] 'New year' >>> d, label = nyd - """ - def __new__(cls, name, indication, date, weekend_hint=rd.MO(1)): + + weekend_hint = rd.MO(1) + + def __new__(cls, date, *args, **kwargs): return super(Holiday, cls).__new__( cls, date.year, date.month, date.day) - def __init__(self, name, indication, date, weekend_hint=rd.MO(1)): + def __init__(self, date, name='Holiday', **kwargs): self.name = name - self.indication = indication - self.weekend_hint = weekend_hint + vars(self).update(kwargs) def __getitem__(self, n): """ @@ -65,17 +66,14 @@ def __iter__(self): def replace(self, *args, **kwargs): replaced = super(Holiday, self).replace(*args, **kwargs) - replaced.name = self.name - replaced.indication = self.indication - replaced.weekend_hint = self.weekend_hint + vars(replaced).update(vars(self)) return replaced def __add__(self, delta): sum = super(Holiday, self).__add__(delta) if sum is NotImplemented: return sum - return self.__class__( - self.name, self.indication, sum, self.weekend_hint) + return self.__class__(sum) @property def observed(self): @@ -95,7 +93,8 @@ def _from_fixed_definition(cls, item): """ if isinstance(item, tuple): month, day, label = item - item = Holiday(label, None, date(2000, month, day)) + any_year = 2000 + item = Holiday(date(any_year, month, day), label) return item @classmethod @@ -104,8 +103,7 @@ def _from_resolved_definition(cls, item): or existing Holiday instance. """ if isinstance(item, tuple): - date, label = item - item = Holiday(label, None, date) + item = Holiday(*item) return item @@ -411,7 +409,8 @@ class WesternCalendar(Calendar): shift_new_years_day = False FIXED_HOLIDAYS = ( - Holiday('New year', 'First day in January', date(2000, 1, 1)), + Holiday( + date(2000, 1, 1), 'New year', indication='First day in January'), ) def get_weekend_days(self): diff --git a/workalendar/tests/test_core.py b/workalendar/tests/test_core.py index a451a278..55fcdc1a 100644 --- a/workalendar/tests/test_core.py +++ b/workalendar/tests/test_core.py @@ -213,10 +213,10 @@ class SimpleObservanceCalendar(Calendar): """ FIXED_HOLIDAYS = ( Holiday( - 'Christmas Eve', 'December 24th', date(2000, 12, 24), + date(2000, 12, 24), 'Christmas Eve', indication='December 24th', weekend_hint=rd.FR(-1), ), - Holiday('Christmas', 'December 25th', date(2000, 12, 25)), + Holiday(date(2000, 12, 25), 'Christmas', indication='December 25th'), ) From 70cab7649f74d77f25b50c611f858ffc3eeb5577 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 09:30:43 -0400 Subject: [PATCH 023/447] Update docstring for Holiday --- workalendar/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index 8a899348..021e7ba1 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -23,13 +23,13 @@ class Holiday(date): create a holiday for New Year's Day, but have it observed on Monday if it falls on a weekend: - >>> nyd = Holiday("New year", "First day in January", date(2014, 1, 1)) + >>> nyd = Holiday(date(2014, 1, 1), "New year") But if New Year's Eve is also a holiday, and it too falls on a weekend, many calendars will have that holiday fall back to the previous friday: - >>> nye = Holiday("New year's eve", "Last day of the year", - ... date(2014, 12, 31), rd.FR(-1)) + >>> nye = Holiday(date(2014, 12, 31), "New year's eve", + ... weekend_hint=rd.FR(-1)) For compatibility, a Holiday may be treated like a tuple of (date, label) From 13b5f830529c377416e5955bea67102861f11090 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 09:33:51 -0400 Subject: [PATCH 024/447] Renamed weekend_hint to observance_hint --HG-- extra : amend_source : 8ecc724430c4ea14fdc35f99b014d23c2fff75d1 --- workalendar/core.py | 14 ++++++++------ workalendar/tests/test_core.py | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index 021e7ba1..17f6b58f 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -29,7 +29,7 @@ class Holiday(date): many calendars will have that holiday fall back to the previous friday: >>> nye = Holiday(date(2014, 12, 31), "New year's eve", - ... weekend_hint=rd.FR(-1)) + ... observance_hint=rd.FR(-1)) For compatibility, a Holiday may be treated like a tuple of (date, label) @@ -40,7 +40,8 @@ class Holiday(date): >>> d, label = nyd """ - weekend_hint = rd.MO(1) + observance_hint = rd.MO(1) + "By default, observe the holiday on the following Monday" def __new__(cls, date, *args, **kwargs): return super(Holiday, cls).__new__( @@ -78,11 +79,12 @@ def __add__(self, delta): @property def observed(self): """ - The date this holiday is observed. If the holiday occurs on a weekend, - it is normally observed on the following Monday. - The weekend hint might be rd.FR(-1), meaning the previous Friday. + The date this holiday is observed. If the holiday occurs on a + non-working day, it may be observed on the following Monday or + another day indicated by the observance_hint, such as rd.FR(-1), + meaning the previous Friday. """ - delta = rd.relativedelta(weekday=self.weekend_hint) + delta = rd.relativedelta(weekday=self.observance_hint) return self + delta if self.weekday() > 4 else self @classmethod diff --git a/workalendar/tests/test_core.py b/workalendar/tests/test_core.py index 55fcdc1a..fdc740ec 100644 --- a/workalendar/tests/test_core.py +++ b/workalendar/tests/test_core.py @@ -214,7 +214,7 @@ class SimpleObservanceCalendar(Calendar): FIXED_HOLIDAYS = ( Holiday( date(2000, 12, 24), 'Christmas Eve', indication='December 24th', - weekend_hint=rd.FR(-1), + observance_hint=rd.FR(-1), ), Holiday(date(2000, 12, 25), 'Christmas', indication='December 25th'), ) From 02d999524c6235555ca42e2ac73325722564bebe Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 09:51:23 -0400 Subject: [PATCH 025/447] Moved the observance calculation to the calendar. --- workalendar/core.py | 21 ++++++++++----------- workalendar/tests/test_core.py | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index 17f6b58f..38f8b905 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -76,17 +76,6 @@ def __add__(self, delta): return sum return self.__class__(sum) - @property - def observed(self): - """ - The date this holiday is observed. If the holiday occurs on a - non-working day, it may be observed on the following Monday or - another day indicated by the observance_hint, such as rd.FR(-1), - meaning the previous Friday. - """ - delta = rd.relativedelta(weekday=self.observance_hint) - return self + delta if self.weekday() > 4 else self - @classmethod def _from_fixed_definition(cls, item): """For backward compatibility, load Holiday object from an item of @@ -152,6 +141,16 @@ def holidays(self, year=None): self._holidays[year] = sorted(temp_calendar) return self._holidays[year] + def get_observed_date(self, holiday): + """ + The date the holiday is observed for this calendar. If the holiday + occurs on a weekend, it may be observed on the following Monday or + another day indicated by the observance_hint, such as rd.FR(-1), + meaning the previous Friday. + """ + delta = rd.relativedelta(weekday=holiday.observance_hint) + return holiday + delta if holiday.weekday() > 4 else holiday + def holidays_set(self, year=None): "Return a quick date index (set)" return set([day for day, label in self.holidays(year)]) diff --git a/workalendar/tests/test_core.py b/workalendar/tests/test_core.py index fdc740ec..639556dd 100644 --- a/workalendar/tests/test_core.py +++ b/workalendar/tests/test_core.py @@ -235,9 +235,9 @@ def test_observance(self): assert len(holidays) == 2 xmas_eve, xmas_day = holidays assert xmas_eve == date(2011, 12, 24) - assert xmas_eve.observed == date(2011, 12, 23) + assert self.cal.get_observed_date(xmas_eve) == date(2011, 12, 23) assert xmas_day == date(2011, 12, 25) - assert xmas_day.observed == date(2011, 12, 26) + assert self.cal.get_observed_date(xmas_day) == date(2011, 12, 26) class IslamicMixinTest(GenericCalendarTest): From 7f033747534b90629edbc035e2fc5116f2f425f1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 09:51:54 -0400 Subject: [PATCH 026/447] Extracted variable for should_shift --- workalendar/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workalendar/core.py b/workalendar/core.py index 38f8b905..33e911a5 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -149,7 +149,8 @@ def get_observed_date(self, holiday): meaning the previous Friday. """ delta = rd.relativedelta(weekday=holiday.observance_hint) - return holiday + delta if holiday.weekday() > 4 else holiday + should_shift = holiday.weekday() > FRI + return holiday + delta if should_shift else holiday def holidays_set(self, year=None): "Return a quick date index (set)" From 5de4069dd3619f4a14de13de96eb49c77080cdec Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 10:05:36 -0400 Subject: [PATCH 027/447] Re-implemented the 'observance_hint' as an 'observance_shift', for which the default is now defined in the Calendar class. --- workalendar/core.py | 27 +++++++++++++++------------ workalendar/tests/test_core.py | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index 33e911a5..5efd7318 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -18,10 +18,8 @@ class Holiday(date): """ - A named holiday with a name, a textual indicated date, the indicated date, - and a weekend hint (used to calculate an observed date). For example, to - create a holiday for New Year's Day, but have it observed on Monday if it - falls on a weekend: + A named holiday with an indicated date, name, and additional keyword + attributes. >>> nyd = Holiday(date(2014, 1, 1), "New year") @@ -29,7 +27,7 @@ class Holiday(date): many calendars will have that holiday fall back to the previous friday: >>> nye = Holiday(date(2014, 12, 31), "New year's eve", - ... observance_hint=rd.FR(-1)) + ... observance_shift=dict(weekday=rd.FR(-1))) For compatibility, a Holiday may be treated like a tuple of (date, label) @@ -40,9 +38,6 @@ class Holiday(date): >>> d, label = nyd """ - observance_hint = rd.MO(1) - "By default, observe the holiday on the following Monday" - def __new__(cls, date, *args, **kwargs): return super(Holiday, cls).__new__( cls, date.year, date.month, date.day) @@ -101,6 +96,12 @@ def _from_resolved_definition(cls, item): class Calendar(object): FIXED_HOLIDAYS = () + observance_shift = dict(weekday=rd.MO(1)) + """ + The shift for the observance of a holiday defined as keyword parameters to + a rd.relativedelta instance. + By default, holidays are shifted to the Monday following the weekend. + """ def __init__(self): self._holidays = {} @@ -144,11 +145,13 @@ def holidays(self, year=None): def get_observed_date(self, holiday): """ The date the holiday is observed for this calendar. If the holiday - occurs on a weekend, it may be observed on the following Monday or - another day indicated by the observance_hint, such as rd.FR(-1), - meaning the previous Friday. + occurs on a weekend, it may be observed on another day as indicated by + the observance_shift. """ - delta = rd.relativedelta(weekday=holiday.observance_hint) + # observance_shift may be overridden in the holiday itself + observance_shift = getattr(holiday, 'observance_shift', + self.observance_shift) + delta = rd.relativedelta(**observance_shift) should_shift = holiday.weekday() > FRI return holiday + delta if should_shift else holiday diff --git a/workalendar/tests/test_core.py b/workalendar/tests/test_core.py index 639556dd..63750d8b 100644 --- a/workalendar/tests/test_core.py +++ b/workalendar/tests/test_core.py @@ -214,7 +214,7 @@ class SimpleObservanceCalendar(Calendar): FIXED_HOLIDAYS = ( Holiday( date(2000, 12, 24), 'Christmas Eve', indication='December 24th', - observance_hint=rd.FR(-1), + observance_shift=dict(weekday=rd.FR(-1)), ), Holiday(date(2000, 12, 25), 'Christmas', indication='December 25th'), ) From 5558e284210296b878d3afea46e6e6dc5f05cc2f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 10:07:42 -0400 Subject: [PATCH 028/447] The observance shift test now honors Calendar.get_weekend_days --- workalendar/core.py | 2 +- workalendar/tests/test_core.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index 5efd7318..d74b33b9 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -152,7 +152,7 @@ def get_observed_date(self, holiday): observance_shift = getattr(holiday, 'observance_shift', self.observance_shift) delta = rd.relativedelta(**observance_shift) - should_shift = holiday.weekday() > FRI + should_shift = holiday.weekday() in self.get_weekend_days() return holiday + delta if should_shift else holiday def holidays_set(self, year=None): diff --git a/workalendar/tests/test_core.py b/workalendar/tests/test_core.py index 63750d8b..38e3fc1a 100644 --- a/workalendar/tests/test_core.py +++ b/workalendar/tests/test_core.py @@ -4,7 +4,7 @@ import dateutil.relativedelta as rd from workalendar.tests import GenericCalendarTest -from workalendar.core import MON, TUE, THU, FRI +from workalendar.core import MON, TUE, THU, FRI, SAT, SUN from workalendar.core import Calendar, LunarCalendar from workalendar.core import IslamicMixin, JalaliMixin from workalendar.core import EphemMixin @@ -219,6 +219,9 @@ class SimpleObservanceCalendar(Calendar): Holiday(date(2000, 12, 25), 'Christmas', indication='December 25th'), ) + def get_weekend_days(self): + return SAT, SUN + class ObservanceCalendarTest(GenericCalendarTest): """ From 1b13f720af1f2356307d9d18ea065e52b7744723 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 10:30:16 -0400 Subject: [PATCH 029/447] Updated Africa tests to reflect new expectation about observed holidays --HG-- extra : amend_source : f8b6166e815bd352874ac388f7450d4fec93df85 --- workalendar/tests/test_africa.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/workalendar/tests/test_africa.py b/workalendar/tests/test_africa.py index f0e18047..b7f4b4e3 100644 --- a/workalendar/tests/test_africa.py +++ b/workalendar/tests/test_africa.py @@ -49,6 +49,7 @@ class SouthAfricaTest(GenericCalendarTest): def test_year_2013(self): holidays = self.cal.holidays_set(2013) + self.assertIn(date(2013, 1, 1), holidays) # new year self.assertIn(date(2013, 3, 21), holidays) # human rights day # good friday, becoming family day @@ -56,18 +57,28 @@ def test_year_2013(self): self.assertIn(date(2013, 4, 27), holidays) # freedom day self.assertIn(date(2013, 5, 1), holidays) # labour day self.assertIn(date(2013, 6, 16), holidays) # youth day - self.assertIn(date(2013, 6, 17), holidays) # youth day - shift + self.assertNotIn(date(2013, 6, 17), holidays) # youth day - observed self.assertIn(date(2013, 8, 9), holidays) # national women's day self.assertIn(date(2013, 9, 24), holidays) # heritage day self.assertIn(date(2013, 12, 16), holidays) # day of reconciliation self.assertIn(date(2013, 12, 25), holidays) # christmas self.assertIn(date(2013, 12, 26), holidays) # day of goodwill + # test that Youth Day is observed on 17-Jun and is not a working day + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2013, 6, 17), observed) + self.assertFalse(self.cal.is_working_day(date(2013, 6, 17))) + def test_year_2014(self): # test shifting holidays = self.cal.holidays_set(2014) self.assertIn(date(2014, 4, 27), holidays) # freedom day - self.assertIn(date(2014, 4, 28), holidays) # freedom day sub + self.assertNotIn(date(2014, 4, 28), holidays) # freedom day - observ + + # test that Freedom Day is observed on 28-Apr and is not a working day + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2014, 4, 28), observed) + self.assertFalse(self.cal.is_working_day(date(2014, 4, 28))) class Madagascar(GenericCalendarTest): From cff68384f1995e1030589370ad586c7a1ff8ab99 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 10:43:58 -0400 Subject: [PATCH 030/447] Update MockCalender to return Holiday instances (as the mocked version does). --- workalendar/tests/test_core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/workalendar/tests/test_core.py b/workalendar/tests/test_core.py index 38e3fc1a..b2b199dd 100644 --- a/workalendar/tests/test_core.py +++ b/workalendar/tests/test_core.py @@ -102,10 +102,10 @@ def test_new_year(self): class MockCalendar(Calendar): def holidays(self, year=None): - return tuple(( - (date(year, 12, 25), 'Christmas'), - (date(year, 1, 1), 'New year'), - )) + return ( + Holiday(date(year, 12, 25), 'Christmas'), + Holiday(date(year, 1, 1), 'New year'), + ) def get_weekend_days(self): return [] # no week-end, yes, it's sad From 4eb7ea9daba76aad9fec4bfb35897dec289b3721 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 10:44:35 -0400 Subject: [PATCH 031/447] Use the observed holiday when resolving working days. --- workalendar/africa.py | 9 +++------ workalendar/core.py | 10 +++++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/workalendar/africa.py b/workalendar/africa.py index c2cf7305..ab5244c4 100644 --- a/workalendar/africa.py +++ b/workalendar/africa.py @@ -115,10 +115,7 @@ def get_family_day(self, year): def get_variable_days(self, year): days = super(SouthAfrica, self).get_variable_days(year) days.append(self.get_family_day(year)) - # compute shifting days - for holiday in self.get_fixed_holidays(year): - if holiday.weekday() == SUN: - sub = holiday + timedelta(days=1) - sub.name += ' substitute' - days.append(sub) return days + + def get_weekend_days(self): + return SUN, diff --git a/workalendar/core.py b/workalendar/core.py index d74b33b9..bcabcd2c 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -194,7 +194,9 @@ def is_working_day(self, day, # Regular rules if day.weekday() in self.get_weekend_days(): return False - if self.is_holiday(day, extra_holidays=extra_holidays): + if extra_holidays and day in extra_holidays: + return False + if self.is_observed_holiday(day): return False return True @@ -213,6 +215,12 @@ def is_holiday(self, day, extra_holidays=None): return True return False + def is_observed_holiday(self, day): + """Return True if it's an observed holiday. + """ + observed = set(map(self.get_observed_date, self.holidays(day.year))) + return day in observed + def add_working_days(self, day, delta, extra_working_days=None, extra_holidays=None): """Add `delta` working days to the date. From 18a539841afe1a597545e8b05e48a398b0ac120d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 10:56:55 -0400 Subject: [PATCH 032/447] Correct pyflakes 'errors' --- workalendar/africa.py | 1 - workalendar/america.py | 4 ++-- workalendar/core.py | 5 ++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/workalendar/africa.py b/workalendar/africa.py index ab5244c4..77d95d3f 100644 --- a/workalendar/africa.py +++ b/workalendar/africa.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import (absolute_import, division, print_function, unicode_literals) -from datetime import timedelta from workalendar.core import WesternCalendar from workalendar.core import SUN from workalendar.core import IslamicMixin, ChristianMixin diff --git a/workalendar/america.py b/workalendar/america.py index 41c5809d..1f7a35c2 100644 --- a/workalendar/america.py +++ b/workalendar/america.py @@ -64,11 +64,11 @@ def get_variable_days(self, year): inauguration_day = date(year, 1, 20) if inauguration_day.weekday() == SUN: inauguration_day = date(year, 1, 21) + ind = "January 20 (or 21st if Sunday) following an election year" h = Holiday( inauguration_day, "Inauguration Day", - indication="January 20 (or 21st if Sunday) following an " - "election year", + indication=ind, ) days.append(h) return days diff --git a/workalendar/core.py b/workalendar/core.py index bcabcd2c..21d50afb 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -149,9 +149,8 @@ def get_observed_date(self, holiday): the observance_shift. """ # observance_shift may be overridden in the holiday itself - observance_shift = getattr(holiday, 'observance_shift', - self.observance_shift) - delta = rd.relativedelta(**observance_shift) + shift = getattr(holiday, 'observance_shift', self.observance_shift) + delta = rd.relativedelta(**shift) should_shift = holiday.weekday() in self.get_weekend_days() return holiday + delta if should_shift else holiday From 5e3e6fe86f50b7626274316dd2de835ba85d96f1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 10:57:35 -0400 Subject: [PATCH 033/447] Thanks to simpler Africa implementation, __add__ is no longer necessary. --- workalendar/core.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index 21d50afb..3b27e305 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -65,12 +65,6 @@ def replace(self, *args, **kwargs): vars(replaced).update(vars(self)) return replaced - def __add__(self, delta): - sum = super(Holiday, self).__add__(delta) - if sum is NotImplemented: - return sum - return self.__class__(sum) - @classmethod def _from_fixed_definition(cls, item): """For backward compatibility, load Holiday object from an item of From 06662e459532be08fd1b112ff907cf5caebdd85a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 11:07:39 -0400 Subject: [PATCH 034/447] Use logical results directly --- workalendar/core.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/workalendar/core.py b/workalendar/core.py index 3b27e305..c4e0a4fd 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -189,9 +189,7 @@ def is_working_day(self, day, return False if extra_holidays and day in extra_holidays: return False - if self.is_observed_holiday(day): - return False - return True + return not self.is_observed_holiday(day) def is_holiday(self, day, extra_holidays=None): """Return True if it's an holiday. @@ -204,9 +202,7 @@ def is_holiday(self, day, extra_holidays=None): if extra_holidays and day in extra_holidays: return True - if day in self.holidays_set(day.year): - return True - return False + return day in self.holidays_set(day.year) def is_observed_holiday(self, day): """Return True if it's an observed holiday. From 8007d28ce169f95e32819edfaa95d30d4129b2f1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 May 2014 11:08:45 -0400 Subject: [PATCH 035/447] Now that Calendar.holidays returns a date instance, holidays_set is straightforward. --- workalendar/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workalendar/core.py b/workalendar/core.py index c4e0a4fd..ec521b75 100644 --- a/workalendar/core.py +++ b/workalendar/core.py @@ -150,7 +150,7 @@ def get_observed_date(self, holiday): def holidays_set(self, year=None): "Return a quick date index (set)" - return set([day for day, label in self.holidays(year)]) + return set(self.holidays(year)) def get_weekend_days(self): """Return a list (or a tuple) of weekdays that are *not* working days. From cb7e29a8381931029abfcd973264800b87d697fd Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 27 Aug 2014 17:53:54 -0400 Subject: [PATCH 036/447] Added tag 0.2dev-20140827 for changeset e624db136314 --- .hgtags | 1 + 1 file changed, 1 insertion(+) create mode 100644 .hgtags diff --git a/.hgtags b/.hgtags new file mode 100644 index 00000000..1bd94047 --- /dev/null +++ b/.hgtags @@ -0,0 +1 @@ +e624db136314c527e764b41ec908c37603e9897a 0.2dev-20140827 From 377c79623d3b9498dcd06dafa33084fd397ff6fb Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 27 Aug 2014 23:11:43 -0400 Subject: [PATCH 037/447] Added tag 0.3-dev-20140827 for changeset 53be893b2eda --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 1bd94047..89a9c3f4 100644 --- a/.hgtags +++ b/.hgtags @@ -1 +1,2 @@ e624db136314c527e764b41ec908c37603e9897a 0.2dev-20140827 +53be893b2edab43aa759608445e92b517195dc01 0.3-dev-20140827 From 0eba4c5697b345f812fba36293316915d372a865 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 30 Aug 2014 18:45:21 -0400 Subject: [PATCH 038/447] Use relative imports to reduce redundancy and to allow project to operate under a different package name. --HG-- extra : amend_source : cb670c4552399641ee2e64de7e72e7bf384b6e4b --- workalendar/africa.py | 6 +++--- workalendar/america.py | 4 ++-- workalendar/asia.py | 4 ++-- workalendar/canada.py | 4 ++-- workalendar/europe.py | 5 +++-- workalendar/oceania.py | 4 ++-- workalendar/tests/__init__.py | 2 +- workalendar/tests/test_africa.py | 8 ++++---- workalendar/tests/test_america.py | 8 ++++---- workalendar/tests/test_asia.py | 8 ++++---- workalendar/tests/test_canada.py | 10 +++++----- workalendar/tests/test_core.py | 10 +++++----- workalendar/tests/test_europe.py | 32 +++++++++++++++---------------- workalendar/tests/test_oceania.py | 22 ++++++++++----------- workalendar/tests/test_usa.py | 4 ++-- workalendar/usa.py | 4 ++-- 16 files changed, 68 insertions(+), 67 deletions(-) diff --git a/workalendar/africa.py b/workalendar/africa.py index 38fa416c..ee9251a2 100644 --- a/workalendar/africa.py +++ b/workalendar/africa.py @@ -2,9 +2,9 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) from datetime import timedelta, date -from workalendar.core import WesternCalendar -from workalendar.core import SUN -from workalendar.core import IslamicMixin, ChristianMixin +from .core import WesternCalendar +from .core import SUN +from .core import IslamicMixin, ChristianMixin class Algeria(WesternCalendar, IslamicMixin): diff --git a/workalendar/america.py b/workalendar/america.py index c3df3f8d..b04497ac 100644 --- a/workalendar/america.py +++ b/workalendar/america.py @@ -3,8 +3,8 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) from datetime import date, timedelta -from workalendar.core import WesternCalendar, ChristianMixin -from workalendar.core import SUN, MON, TUE, WED, FRI, SAT +from .core import WesternCalendar, ChristianMixin +from .core import SUN, MON, TUE, WED, FRI, SAT class Brazil(WesternCalendar, ChristianMixin): diff --git a/workalendar/asia.py b/workalendar/asia.py index 71c687e0..5243f868 100644 --- a/workalendar/asia.py +++ b/workalendar/asia.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from datetime import timedelta -from workalendar.core import LunarCalendar, WesternCalendar, Calendar -from workalendar.core import MON, FRI, SAT, IslamicMixin, EphemMixin +from .core import LunarCalendar, WesternCalendar, Calendar +from .core import MON, FRI, SAT, IslamicMixin, EphemMixin class SouthKorea(LunarCalendar): diff --git a/workalendar/canada.py b/workalendar/canada.py index a0d81f79..27c42874 100644 --- a/workalendar/canada.py +++ b/workalendar/canada.py @@ -3,8 +3,8 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) from datetime import date -from workalendar.core import WesternCalendar, ChristianMixin, Calendar -from workalendar.core import SUN, MON, SAT +from .core import WesternCalendar, ChristianMixin, Calendar +from .core import SUN, MON, SAT class Canada(WesternCalendar, ChristianMixin): diff --git a/workalendar/europe.py b/workalendar/europe.py index 179d6e27..c3ae612f 100644 --- a/workalendar/europe.py +++ b/workalendar/europe.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- + from datetime import date, timedelta -from workalendar.core import WesternCalendar, ChristianMixin, OrthodoxMixin -from workalendar.core import THU, MON, FRI, SAT +from .core import WesternCalendar, ChristianMixin, OrthodoxMixin +from .core import THU, MON, FRI, SAT class CzechRepublic(WesternCalendar, ChristianMixin): diff --git a/workalendar/oceania.py b/workalendar/oceania.py index e138c3a3..4682cfea 100644 --- a/workalendar/oceania.py +++ b/workalendar/oceania.py @@ -1,5 +1,5 @@ -from workalendar.core import WesternCalendar, ChristianMixin -from workalendar.core import MON, TUE, FRI +from .core import WesternCalendar, ChristianMixin +from .core import MON, TUE, FRI from datetime import date diff --git a/workalendar/tests/__init__.py b/workalendar/tests/__init__.py index 3107c1a4..d15fdcac 100644 --- a/workalendar/tests/__init__.py +++ b/workalendar/tests/__init__.py @@ -2,7 +2,7 @@ from datetime import date from unittest import TestCase -from workalendar.core import Calendar +from ..core import Calendar class GenericCalendarTest(TestCase): diff --git a/workalendar/tests/test_africa.py b/workalendar/tests/test_africa.py index f0e18047..4aacf573 100644 --- a/workalendar/tests/test_africa.py +++ b/workalendar/tests/test_africa.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from datetime import date -from workalendar.tests import GenericCalendarTest -from workalendar.africa import Benin, Algeria -from workalendar.africa import SouthAfrica, IvoryCoast -from workalendar.africa import SaoTomeAndPrincipe, Madagascar +from . import GenericCalendarTest +from ..africa import Benin, Algeria +from ..africa import SouthAfrica, IvoryCoast +from ..africa import SaoTomeAndPrincipe, Madagascar class AlgeriaTest(GenericCalendarTest): diff --git a/workalendar/tests/test_america.py b/workalendar/tests/test_america.py index bb913ee8..806ab81c 100644 --- a/workalendar/tests/test_america.py +++ b/workalendar/tests/test_america.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from datetime import date -from workalendar.tests import GenericCalendarTest -from workalendar.america import Brazil, BrazilSaoPauloState -from workalendar.america import BrazilSaoPauloCity -from workalendar.america import Mexico, Chile, Panama +from . import GenericCalendarTest +from ..america import Brazil, BrazilSaoPauloState +from ..america import BrazilSaoPauloCity +from ..america import Mexico, Chile, Panama class BrazilTest(GenericCalendarTest): diff --git a/workalendar/tests/test_asia.py b/workalendar/tests/test_asia.py index 98625833..3087a381 100644 --- a/workalendar/tests/test_asia.py +++ b/workalendar/tests/test_asia.py @@ -1,8 +1,8 @@ from datetime import date -from workalendar.tests import GenericCalendarTest -from workalendar.asia import SouthKorea, Japan -from workalendar.asia import Qatar -from workalendar.asia import Taiwan +from . import GenericCalendarTest +from ..asia import SouthKorea, Japan +from ..asia import Qatar +from ..asia import Taiwan class SouthKoreaTest(GenericCalendarTest): diff --git a/workalendar/tests/test_canada.py b/workalendar/tests/test_canada.py index f34ce70e..53e25fef 100644 --- a/workalendar/tests/test_canada.py +++ b/workalendar/tests/test_canada.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- from datetime import date -from workalendar.tests import GenericCalendarTest -from workalendar.canada import Canada, Ontario, Quebec, BritishColumbia -from workalendar.canada import Alberta, Saskatchewan, Manitoba, NewBrunswick -from workalendar.canada import NovaScotia, PrinceEdwardIsland, Newfoundland -from workalendar.canada import Yukon, NorthwestTerritories, Nunavut +from . import GenericCalendarTest +from ..canada import Canada, Ontario, Quebec, BritishColumbia +from ..canada import Alberta, Saskatchewan, Manitoba, NewBrunswick +from ..canada import NovaScotia, PrinceEdwardIsland, Newfoundland +from ..canada import Yukon, NorthwestTerritories, Nunavut class CanadaTest(GenericCalendarTest): diff --git a/workalendar/tests/test_core.py b/workalendar/tests/test_core.py index 7dfa1d66..75839c34 100644 --- a/workalendar/tests/test_core.py +++ b/workalendar/tests/test_core.py @@ -1,10 +1,10 @@ from datetime import date from datetime import datetime -from workalendar.tests import GenericCalendarTest -from workalendar.core import MON, TUE, THU, FRI -from workalendar.core import Calendar, LunarCalendar, WesternCalendar -from workalendar.core import IslamicMixin, JalaliMixin, ChristianMixin -from workalendar.core import EphemMixin +from . import GenericCalendarTest +from ..core import MON, TUE, THU, FRI +from ..core import Calendar, LunarCalendar, WesternCalendar +from ..core import IslamicMixin, JalaliMixin, ChristianMixin +from ..core import EphemMixin class CalendarTest(GenericCalendarTest): diff --git a/workalendar/tests/test_europe.py b/workalendar/tests/test_europe.py index d122ac43..8782c5f6 100644 --- a/workalendar/tests/test_europe.py +++ b/workalendar/tests/test_europe.py @@ -1,20 +1,20 @@ from datetime import date -from workalendar.tests import GenericCalendarTest -from workalendar.europe import CzechRepublic -from workalendar.europe import Finland -from workalendar.europe import Sweden -from workalendar.europe import France, FranceAlsaceMoselle -from workalendar.europe import Greece -from workalendar.europe import Hungary -from workalendar.europe import Iceland -from workalendar.europe import Italy -from workalendar.europe import Norway -from workalendar.europe import Poland -from workalendar.europe import UnitedKingdom -from workalendar.europe import UnitedKingdomNorthernIreland -from workalendar.europe import EuropeanCentralBank -from workalendar.europe import Belgium -from workalendar.europe import (Germany, BadenWurttemberg, Bavaria, Berlin, +from . import GenericCalendarTest +from ..europe import CzechRepublic +from ..europe import Finland +from ..europe import Sweden +from ..europe import France, FranceAlsaceMoselle +from ..europe import Greece +from ..europe import Hungary +from ..europe import Iceland +from ..europe import Italy +from ..europe import Norway +from ..europe import Poland +from ..europe import UnitedKingdom +from ..europe import UnitedKingdomNorthernIreland +from ..europe import EuropeanCentralBank +from ..europe import Belgium +from ..europe import (Germany, BadenWurttemberg, Bavaria, Berlin, Brandenburg, Bremen, Hamburg, Hesse, MecklenburgVorpommern, LowerSaxony, NorthRhineWestphalia, RhinelandPalatinate, diff --git a/workalendar/tests/test_oceania.py b/workalendar/tests/test_oceania.py index 2a96ccb5..3dfd384c 100644 --- a/workalendar/tests/test_oceania.py +++ b/workalendar/tests/test_oceania.py @@ -1,16 +1,16 @@ from datetime import date -from workalendar.tests import GenericCalendarTest -from workalendar.oceania import Australia -from workalendar.oceania import AustraliaCapitalTerritory -from workalendar.oceania import AustraliaNewSouthWales -from workalendar.oceania import AustraliaNorthernTerritory -from workalendar.oceania import AustraliaQueensland -from workalendar.oceania import SouthAustralia -from workalendar.oceania import Tasmania, Hobart -from workalendar.oceania import Victoria -from workalendar.oceania import WesternAustralia -from workalendar.oceania import MarshallIslands +from . import GenericCalendarTest +from ..oceania import Australia +from ..oceania import AustraliaCapitalTerritory +from ..oceania import AustraliaNewSouthWales +from ..oceania import AustraliaNorthernTerritory +from ..oceania import AustraliaQueensland +from ..oceania import SouthAustralia +from ..oceania import Tasmania, Hobart +from ..oceania import Victoria +from ..oceania import WesternAustralia +from ..oceania import MarshallIslands class AustraliaTest(GenericCalendarTest): diff --git a/workalendar/tests/test_usa.py b/workalendar/tests/test_usa.py index 252cb966..d50a6c35 100644 --- a/workalendar/tests/test_usa.py +++ b/workalendar/tests/test_usa.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from datetime import date -from workalendar.tests import GenericCalendarTest -from workalendar.usa import (UnitedStates, Alabama, Florida, Arkansas, +from . import GenericCalendarTest +from ..usa import (UnitedStates, Alabama, Florida, Arkansas, Alaska, Arizona, California, Colorado, Connecticut, Delaware, Georgia, Indiana, Illinois, Idaho, Iowa, Kansas, Kentucky, diff --git a/workalendar/usa.py b/workalendar/usa.py index b74dc212..fb83d980 100644 --- a/workalendar/usa.py +++ b/workalendar/usa.py @@ -3,8 +3,8 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) from datetime import date, timedelta -from workalendar.core import WesternCalendar, ChristianMixin, Calendar -from workalendar.core import SUN, MON, TUE, WED, THU, FRI, SAT +from .core import WesternCalendar, ChristianMixin, Calendar +from .core import SUN, MON, TUE, WED, THU, FRI, SAT NONE, NEAREST_WEEKDAY, MONDAY = range(3) From 807073ba3231d873f40e4146fcad9f8e89fb39ae Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 21 Sep 2014 10:01:42 -0400 Subject: [PATCH 039/447] Rename project to calendra --HG-- rename : workalendar/__init__.py => calendra/__init__.py rename : workalendar/africa.py => calendra/africa.py rename : workalendar/america.py => calendra/america.py rename : workalendar/asia.py => calendra/asia.py rename : workalendar/canada.py => calendra/canada.py rename : workalendar/core.py => calendra/core.py rename : workalendar/europe.py => calendra/europe.py rename : workalendar/oceania.py => calendra/oceania.py rename : workalendar/tests/__init__.py => calendra/tests/__init__.py rename : workalendar/tests/test_africa.py => calendra/tests/test_africa.py rename : workalendar/tests/test_america.py => calendra/tests/test_america.py rename : workalendar/tests/test_asia.py => calendra/tests/test_asia.py rename : workalendar/tests/test_canada.py => calendra/tests/test_canada.py rename : workalendar/tests/test_core.py => calendra/tests/test_core.py rename : workalendar/tests/test_europe.py => calendra/tests/test_europe.py rename : workalendar/tests/test_oceania.py => calendra/tests/test_oceania.py rename : workalendar/tests/test_usa.py => calendra/tests/test_usa.py rename : workalendar/usa.py => calendra/usa.py extra : amend_source : b74852cea67227d2f8bac4bb7322cbf170b0c313 --- CONTRIBUTING.rst | 30 +++++++++---------- README.rst | 25 ++++++++++------ {workalendar => calendra}/__init__.py | 0 {workalendar => calendra}/africa.py | 0 {workalendar => calendra}/america.py | 0 {workalendar => calendra}/asia.py | 0 {workalendar => calendra}/canada.py | 0 {workalendar => calendra}/core.py | 0 {workalendar => calendra}/europe.py | 0 {workalendar => calendra}/oceania.py | 0 {workalendar => calendra}/tests/__init__.py | 0 .../tests/test_africa.py | 0 .../tests/test_america.py | 0 {workalendar => calendra}/tests/test_asia.py | 0 .../tests/test_canada.py | 0 {workalendar => calendra}/tests/test_core.py | 0 .../tests/test_europe.py | 0 .../tests/test_oceania.py | 0 {workalendar => calendra}/tests/test_usa.py | 0 {workalendar => calendra}/usa.py | 0 setup.py | 12 ++++---- 21 files changed, 37 insertions(+), 30 deletions(-) rename {workalendar => calendra}/__init__.py (100%) rename {workalendar => calendra}/africa.py (100%) rename {workalendar => calendra}/america.py (100%) rename {workalendar => calendra}/asia.py (100%) rename {workalendar => calendra}/canada.py (100%) rename {workalendar => calendra}/core.py (100%) rename {workalendar => calendra}/europe.py (100%) rename {workalendar => calendra}/oceania.py (100%) rename {workalendar => calendra}/tests/__init__.py (100%) rename {workalendar => calendra}/tests/test_africa.py (100%) rename {workalendar => calendra}/tests/test_america.py (100%) rename {workalendar => calendra}/tests/test_asia.py (100%) rename {workalendar => calendra}/tests/test_canada.py (100%) rename {workalendar => calendra}/tests/test_core.py (100%) rename {workalendar => calendra}/tests/test_europe.py (100%) rename {workalendar => calendra}/tests/test_oceania.py (100%) rename {workalendar => calendra}/tests/test_usa.py (100%) rename {workalendar => calendra}/usa.py (100%) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c017f676..22d250c7 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,17 +1,17 @@ -========================= -Contribute to Workalendar -========================= +====================== +Contribute to Calendra +====================== Use it (and test it) ==================== -If you are using ``workalendar``, you are already contributing to it. As long +If you are using ``calendra``, you are already contributing to it. As long as you are able to check its result, compare the designated working days and holidays to the reality, and make sure these are right, you're helping. If any of the computed holidays for the country / area your are using is **wrong**, please report -`it using the Github issues `_. +`it using the Github issues `_. Report an issue =============== @@ -19,7 +19,7 @@ Report an issue If you think you've found a bug you can report an issue. In order to help us sort this out, please follow the guidelines: -* Tell us which ``workalendar`` version (master, PyPI release) you are using. +* Tell us which ``calendra`` version (master, PyPI release) you are using. * Tell us which Python version you are using, and your platform. * Give us extensive details on the country / area Calendar, the exact date(s) that was (were) computed and the one(s) that should have been the correct result. * If possible, please provide us a reliable source about the designated country / area calendar, where we could effectively check that we were wrong about a date, and giving us a way to patch our code properly so we can fix the bug. @@ -28,12 +28,12 @@ sort this out, please follow the guidelines: Adding new calendars ==================== -Since ``workalendar`` is mainly built around configuration variables and generic +Since ``calendra`` is mainly built around configuration variables and generic methods, it's not that difficult to add a calendar to our codebase. A few **mandatory** steps should be observed: 1. Fork the repository and create a new branch named after the calendar you want to implement, -2. Add a test class to the workalendar test suite that checks holidays, +2. Add a test class to the test suite that checks holidays, 3. Implement the class using the core class APIs as much as possible. Test it until all tests pass. 4. Make a nice pull-request we'll be glad to review and merge when it's perfect. @@ -66,12 +66,12 @@ Here is a list of the holidays in *Zhraa*: Getting ready ############# -You'll need to install ``workalendar`` dependencies beforehand. What's great is +You'll need to install ``calendra`` dependencies beforehand. What's great is that you'll use virtualenv to set it up. Or even better: ``virtualenvwrapper``. -Just go in your working copy (cloned from github) of workalendar and type, for +Just go in your working copy (cloned from github) of calendra and type, for example:: - mkvirtualenv WORKALENDAR + mkvirtualenv CALENDRA pip install -e ./ @@ -79,17 +79,17 @@ Test-driven start ################# -Let's prepare the Zhraa class. In the ``workalendar/oceania.py`` file, add +Let's prepare the Zhraa class. In the ``calendra/oceania.py`` file, add a class like this:: class Zhraa(WesternCalendar): pass -Now, we're building a test class. Edit the ``workalendar/tests/test_oceania.py`` +Now, we're building a test class. Edit the ``calendra/tests/test_oceania.py`` file and add the following code:: - from workalendar.oceania import Zhraa + from ..oceania import Zhraa # snip... class ZhraaTest(GenericCalendarTest): @@ -110,7 +110,7 @@ this will fail, since we haven't implemented anything yet. Install tox using the following command:: - workon WORKALENDAR + workon CALENDRA pip install tox With the ``WesternCalendar`` base class you have at least one holiday as a diff --git a/README.rst b/README.rst index e92654e8..be273fe7 100644 --- a/README.rst +++ b/README.rst @@ -1,20 +1,27 @@ -=========== -Workalendar -=========== +======== +Calendra +======== Overview ======== -Workalendar is a Python module that offers classes able to handle calendars, +Calendra is a Python module that offers classes able to handle calendars, list legal / religious holidays and gives working-day-related computation functions. +History +======= + +Calendra is a fork of `Workalendar `_ +designed to be more extensible and introspectable, adding interfaces where +`Workalendar is philosophically opposed for the sake of simplicity +`_. Status ====== -This is barely beta. Please consider this module as a work in progres. - +The project is stable and in production use. Calendra follows the principles +of `semver `_ for released verisons. Usage sample ============ @@ -55,7 +62,7 @@ Tests Travis status: -.. image:: https://api.travis-ci.org/novapost/workalendar.png +.. image:: https://api.travis-ci.org/jaraco/calendra.png To run test, just install tox with ``pip install tox`` and run:: @@ -135,8 +142,8 @@ the official data provided by the adequate authorities. Contributing ============ -Please read our `CONTRIBUTING.rst `_ -document to discover how you can contribute to ``workalendar``. Pull-requests +Please read our `CONTRIBUTING.rst `_ +document to discover how you can contribute to ``calendra``. Pull-requests are very welcome. License diff --git a/workalendar/__init__.py b/calendra/__init__.py similarity index 100% rename from workalendar/__init__.py rename to calendra/__init__.py diff --git a/workalendar/africa.py b/calendra/africa.py similarity index 100% rename from workalendar/africa.py rename to calendra/africa.py diff --git a/workalendar/america.py b/calendra/america.py similarity index 100% rename from workalendar/america.py rename to calendra/america.py diff --git a/workalendar/asia.py b/calendra/asia.py similarity index 100% rename from workalendar/asia.py rename to calendra/asia.py diff --git a/workalendar/canada.py b/calendra/canada.py similarity index 100% rename from workalendar/canada.py rename to calendra/canada.py diff --git a/workalendar/core.py b/calendra/core.py similarity index 100% rename from workalendar/core.py rename to calendra/core.py diff --git a/workalendar/europe.py b/calendra/europe.py similarity index 100% rename from workalendar/europe.py rename to calendra/europe.py diff --git a/workalendar/oceania.py b/calendra/oceania.py similarity index 100% rename from workalendar/oceania.py rename to calendra/oceania.py diff --git a/workalendar/tests/__init__.py b/calendra/tests/__init__.py similarity index 100% rename from workalendar/tests/__init__.py rename to calendra/tests/__init__.py diff --git a/workalendar/tests/test_africa.py b/calendra/tests/test_africa.py similarity index 100% rename from workalendar/tests/test_africa.py rename to calendra/tests/test_africa.py diff --git a/workalendar/tests/test_america.py b/calendra/tests/test_america.py similarity index 100% rename from workalendar/tests/test_america.py rename to calendra/tests/test_america.py diff --git a/workalendar/tests/test_asia.py b/calendra/tests/test_asia.py similarity index 100% rename from workalendar/tests/test_asia.py rename to calendra/tests/test_asia.py diff --git a/workalendar/tests/test_canada.py b/calendra/tests/test_canada.py similarity index 100% rename from workalendar/tests/test_canada.py rename to calendra/tests/test_canada.py diff --git a/workalendar/tests/test_core.py b/calendra/tests/test_core.py similarity index 100% rename from workalendar/tests/test_core.py rename to calendra/tests/test_core.py diff --git a/workalendar/tests/test_europe.py b/calendra/tests/test_europe.py similarity index 100% rename from workalendar/tests/test_europe.py rename to calendra/tests/test_europe.py diff --git a/workalendar/tests/test_oceania.py b/calendra/tests/test_oceania.py similarity index 100% rename from workalendar/tests/test_oceania.py rename to calendra/tests/test_oceania.py diff --git a/workalendar/tests/test_usa.py b/calendra/tests/test_usa.py similarity index 100% rename from workalendar/tests/test_usa.py rename to calendra/tests/test_usa.py diff --git a/workalendar/usa.py b/calendra/usa.py similarity index 100% rename from workalendar/usa.py rename to calendra/usa.py diff --git a/setup.py b/setup.py index c62bba03..f43c4840 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ def read_relative_file(filename): with io.open(path, encoding='utf-8') as f: return f.read() -NAME = 'workalendar' +NAME = 'calendra' DESCRIPTION = 'Worldwide holidays and working days helper and toolkit.' REQUIREMENTS = [ 'python-dateutil', @@ -27,7 +27,7 @@ def read_relative_file(filename): 'pytz', 'pyCalverter', ] -__VERSION__ = '0.3-dev' +__VERSION__ = '1.0' if PY2: REQUIREMENTS.append('pyephem') @@ -37,12 +37,12 @@ def read_relative_file(filename): params = dict( name=NAME, description=DESCRIPTION, - packages=['workalendar'], + packages=['calendra'], version=__VERSION__, long_description=read_relative_file('README.rst'), - author='Bruno Bord', - author_email='bruno.bord@novapost.fr', - url='https://github.com/novapost/workalendar', + author='Jason R. Coombs', + author_email='jaraco@jaraco.com', + url='https://github.com/jaraco/calendra', license='MIT License', include_package_data=True, install_requires=REQUIREMENTS, From 4ae1dbd9f20ab3c0ea0062acb162e61aa7c48644 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 21 Sep 2014 11:05:37 -0400 Subject: [PATCH 040/447] Update changelog --- CHANGELOG | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c7c396ab..e9cc747c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,19 @@ CHANGELOG ========= -0.3 (unreleased) +1.0 (2014-09-21) ---------------- +Initial release of Calendra based on Workalendar 0.2. + +- Adds Holiday class per `Workalendar Pull Request #72 + `_. Adds support for giving + holidays a more rich description and better resolution of observed versus + indicated holidays. See the pull request for detail on the motivation and + implementation. See the usa.UnitedStates calendar for example usage. + +Includes these changes slated for workalendar 0.3: + - Germany calendar added, thx to @rndusr - Support building on systems where LANG=C (Ubuntu) #92 From c082bb273761a8c50d6ebbe6bf2c0c832e61d405 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 21 Sep 2014 11:20:06 -0400 Subject: [PATCH 041/447] Use hgtools to leverage VCS version --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f43c4840..af1d1aa1 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,6 @@ def read_relative_file(filename): 'pytz', 'pyCalverter', ] -__VERSION__ = '1.0' if PY2: REQUIREMENTS.append('pyephem') @@ -38,7 +37,7 @@ def read_relative_file(filename): name=NAME, description=DESCRIPTION, packages=['calendra'], - version=__VERSION__, + use_vcs_version=True, long_description=read_relative_file('README.rst'), author='Jason R. Coombs', author_email='jaraco@jaraco.com', @@ -57,6 +56,9 @@ def read_relative_file(filename): 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', ], + setup_requires=[ + 'hgtools', + ], ) if __name__ == '__main__': From 268bb07fb177a380f23acdb2dc72722be790a25e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 21 Sep 2014 11:20:08 -0400 Subject: [PATCH 042/447] Added tag 1.0 for changeset 62b9caa99596 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 89a9c3f4..0ec09329 100644 --- a/.hgtags +++ b/.hgtags @@ -1,2 +1,3 @@ e624db136314c527e764b41ec908c37603e9897a 0.2dev-20140827 53be893b2edab43aa759608445e92b517195dc01 0.3-dev-20140827 +62b9caa9959662d71f3a5a83a6cd2f39833c3d54 1.0 From 081fb15239812c5cbcb5df891fd60bdf9293316a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 23 Sep 2014 16:12:02 -0400 Subject: [PATCH 043/447] Update Trove classifier to indicate stability. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index af1d1aa1..7df6a776 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ def read_relative_file(filename): install_requires=REQUIREMENTS, zip_safe=False, classifiers=[ - 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', From af6a8eba72b15eeb8d6b5389026bc9a45a98b267 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 23 Sep 2014 16:16:03 -0400 Subject: [PATCH 044/447] Package is zip-safe --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7df6a776..04fe41e9 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ def read_relative_file(filename): license='MIT License', include_package_data=True, install_requires=REQUIREMENTS, - zip_safe=False, + zip_safe=True, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', From 16b360f778722815e335917eb0e8d67937b9486e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 23 Sep 2014 23:27:28 -0400 Subject: [PATCH 045/447] Update tox config for project rename --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 5a0b5e4c..f5b5f655 100644 --- a/tox.ini +++ b/tox.ini @@ -2,17 +2,17 @@ envlist = py27,flake8,py33 [testenv] -deps = +deps = nose coverage commands = python setup.py develop - nosetests -sv --with-coverage --cover-package=workalendar workalendar + nosetests -sv --with-coverage --cover-package=calendra calendra pip freeze [testenv:flake8] deps = flake8 -commands = flake8 workalendar +commands = flake8 calendra From a3d67cf3fd499daf3d438daf0fc93a2e7c114eb2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 23 Sep 2014 23:36:26 -0400 Subject: [PATCH 046/447] Normalize indentation. Use single-level indent for nicer wrapping. --- calendra/tests/test_europe.py | 13 +++++++------ calendra/tests/test_usa.py | 23 +++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/calendra/tests/test_europe.py b/calendra/tests/test_europe.py index 8782c5f6..0ae5db98 100644 --- a/calendra/tests/test_europe.py +++ b/calendra/tests/test_europe.py @@ -1,4 +1,5 @@ from datetime import date + from . import GenericCalendarTest from ..europe import CzechRepublic from ..europe import Finland @@ -14,12 +15,12 @@ from ..europe import UnitedKingdomNorthernIreland from ..europe import EuropeanCentralBank from ..europe import Belgium -from ..europe import (Germany, BadenWurttemberg, Bavaria, Berlin, - Brandenburg, Bremen, Hamburg, Hesse, - MecklenburgVorpommern, LowerSaxony, - NorthRhineWestphalia, RhinelandPalatinate, - Saarland, Saxony, SaxonyAnhalt, - SchleswigHolstein, Thuringia) +from ..europe import ( + Germany, BadenWurttemberg, Bavaria, Berlin, Brandenburg, Bremen, Hamburg, + Hesse, MecklenburgVorpommern, LowerSaxony, NorthRhineWestphalia, + RhinelandPalatinate, Saarland, Saxony, SaxonyAnhalt, SchleswigHolstein, + Thuringia, +) class CzechRepublicTest(GenericCalendarTest): diff --git a/calendra/tests/test_usa.py b/calendra/tests/test_usa.py index d50a6c35..f2b78c16 100644 --- a/calendra/tests/test_usa.py +++ b/calendra/tests/test_usa.py @@ -1,18 +1,17 @@ # -*- coding: utf-8 -*- from datetime import date + from . import GenericCalendarTest -from ..usa import (UnitedStates, Alabama, Florida, Arkansas, - Alaska, Arizona, California, Colorado, - Connecticut, Delaware, Georgia, Indiana, - Illinois, Idaho, Iowa, Kansas, Kentucky, - Louisiana, Maine, Maryland, Massachusetts, - Minnesota, Michigan, Mississippi, Missouri, - Montana, Nebraska, Nevada, NewHampshire, - NewJersey, NewMexico, NewYork, NorthCarolina, - NorthDakota, Ohio, Oklahoma, Oregon, Pennsylvania, - RhodeIsland, SouthCarolina, SouthDakota, - Tennessee, Texas, Utah, Vermont, Virginia, - Washington, WestVirginia, Wisconsin, Wyoming) +from ..usa import ( + UnitedStates, Alabama, Florida, Arkansas, Alaska, Arizona, California, + Colorado, Connecticut, Delaware, Georgia, Indiana, Illinois, Idaho, Iowa, + Kansas, Kentucky, Louisiana, Maine, Maryland, Massachusetts, Minnesota, + Michigan, Mississippi, Missouri, Montana, Nebraska, Nevada, NewHampshire, + NewJersey, NewMexico, NewYork, NorthCarolina, NorthDakota, Ohio, Oklahoma, + Oregon, Pennsylvania, RhodeIsland, SouthCarolina, SouthDakota, Tennessee, + Texas, Utah, Vermont, Virginia, Washington, WestVirginia, Wisconsin, + Wyoming, +) class UnitedStatesTest(GenericCalendarTest): From d24a4f5b897ab9ce553245a7b85cb8d420a54b5a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 23 Sep 2014 23:44:48 -0400 Subject: [PATCH 047/447] Remove whitespace making flake8 fail --- workalendar/tests/test_europe.py | 10 +++++----- workalendar/tests/test_usa.py | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/workalendar/tests/test_europe.py b/workalendar/tests/test_europe.py index 8782c5f6..f529a9af 100644 --- a/workalendar/tests/test_europe.py +++ b/workalendar/tests/test_europe.py @@ -15,11 +15,11 @@ from ..europe import EuropeanCentralBank from ..europe import Belgium from ..europe import (Germany, BadenWurttemberg, Bavaria, Berlin, - Brandenburg, Bremen, Hamburg, Hesse, - MecklenburgVorpommern, LowerSaxony, - NorthRhineWestphalia, RhinelandPalatinate, - Saarland, Saxony, SaxonyAnhalt, - SchleswigHolstein, Thuringia) + Brandenburg, Bremen, Hamburg, Hesse, + MecklenburgVorpommern, LowerSaxony, + NorthRhineWestphalia, RhinelandPalatinate, + Saarland, Saxony, SaxonyAnhalt, + SchleswigHolstein, Thuringia) class CzechRepublicTest(GenericCalendarTest): diff --git a/workalendar/tests/test_usa.py b/workalendar/tests/test_usa.py index d50a6c35..6c206952 100644 --- a/workalendar/tests/test_usa.py +++ b/workalendar/tests/test_usa.py @@ -2,17 +2,17 @@ from datetime import date from . import GenericCalendarTest from ..usa import (UnitedStates, Alabama, Florida, Arkansas, - Alaska, Arizona, California, Colorado, - Connecticut, Delaware, Georgia, Indiana, - Illinois, Idaho, Iowa, Kansas, Kentucky, - Louisiana, Maine, Maryland, Massachusetts, - Minnesota, Michigan, Mississippi, Missouri, - Montana, Nebraska, Nevada, NewHampshire, - NewJersey, NewMexico, NewYork, NorthCarolina, - NorthDakota, Ohio, Oklahoma, Oregon, Pennsylvania, - RhodeIsland, SouthCarolina, SouthDakota, - Tennessee, Texas, Utah, Vermont, Virginia, - Washington, WestVirginia, Wisconsin, Wyoming) + Alaska, Arizona, California, Colorado, + Connecticut, Delaware, Georgia, Indiana, + Illinois, Idaho, Iowa, Kansas, Kentucky, + Louisiana, Maine, Maryland, Massachusetts, + Minnesota, Michigan, Mississippi, Missouri, + Montana, Nebraska, Nevada, NewHampshire, + NewJersey, NewMexico, NewYork, NorthCarolina, + NorthDakota, Ohio, Oklahoma, Oregon, Pennsylvania, + RhodeIsland, SouthCarolina, SouthDakota, + Tennessee, Texas, Utah, Vermont, Virginia, + Washington, WestVirginia, Wisconsin, Wyoming) class UnitedStatesTest(GenericCalendarTest): From e1fdc280997aa6099d70ab217e4f98a81b51130a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Nov 2014 17:42:44 -0500 Subject: [PATCH 048/447] Add support for an observe_after property on a Holiday. --- calendra/core.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/calendra/core.py b/calendra/core.py index 10c7d180..0e517f2c 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -141,12 +141,20 @@ def get_observed_date(self, holiday): The date the holiday is observed for this calendar. If the holiday occurs on a weekend, it may be observed on another day as indicated by the observance_shift. + + The holiday may also specify an 'observe_after' such that it is always + shifted after a preceding holiday. For example, Boxing day is always + observed after Christmas Day is observed. """ # observance_shift may be overridden in the holiday itself shift = getattr(holiday, 'observance_shift', self.observance_shift) delta = rd.relativedelta(**shift) should_shift = holiday.weekday() in self.get_weekend_days() - return holiday + delta if should_shift else holiday + shifted = holiday + delta if should_shift else holiday + precedent = getattr(holiday, 'observe_after', None) + while precedent and shifted <= self.get_observed_date(precedent): + shifted += timedelta(days=1) + return shifted def holidays_set(self, year=None): "Return a quick date index (set)" From 62c09ad53b82af1f1409e6cb4f9da52ed016a34c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Nov 2014 17:54:01 -0500 Subject: [PATCH 049/447] Update UnitedKingdom to support indicated and observed holidays. --- calendra/core.py | 12 +++++++-- calendra/europe.py | 48 +++++++++++++++-------------------- calendra/tests/test_europe.py | 8 +++--- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/calendra/core.py b/calendra/core.py index 0e517f2c..27750628 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -389,12 +389,20 @@ def get_variable_days(self, year): days.append((date(year, 11, 1), "All Saints Day")) if self.include_immaculate_conception: days.append((date(year, 12, 8), "Immaculate Conception")) + christmas = None if self.include_christmas: - days.append((date(year, 12, 25), "Christmas Day")) + christmas = Holiday(date(year, 12, 25), "Christmas Day") + days.append(christmas) if self.include_christmas_eve: days.append((date(year, 12, 24), "Christmas Eve")) if self.include_boxing_day: - days.append((date(year, 12, 26), self.boxing_day_label)) + boxing_day = Holiday( + date(year, 12, 26), + self.boxing_day_label, + indication="Day after Christmas", + observe_after=christmas + ) + days.append(boxing_day) if self.include_ascension: days.append(( self.get_ascension_thursday(year), "Ascension Thursday")) diff --git a/calendra/europe.py b/calendra/europe.py index c3ae612f..23abfc0a 100644 --- a/calendra/europe.py +++ b/calendra/europe.py @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- from datetime import date, timedelta + +from dateutil import relativedelta as rd + from .core import WesternCalendar, ChristianMixin, OrthodoxMixin from .core import THU, MON, FRI, SAT +from .core import Holiday class CzechRepublic(WesternCalendar, ChristianMixin): @@ -275,35 +279,25 @@ class UnitedKingdom(WesternCalendar, ChristianMixin): include_boxing_day = True shift_new_years_day = True - def get_early_may_bank_holiday(self, year): - return ( - UnitedKingdom.get_nth_weekday_in_month(year, 5, MON), - "Early May Bank Holiday" - ) - - def get_spring_bank_holiday(self, year): - return ( - UnitedKingdom.get_last_weekday_in_month(year, 5, MON), - "Spring Bank Holiday" - ) - - def get_late_summer_bank_holiday(self, year): - return ( - UnitedKingdom.get_last_weekday_in_month(year, 8, MON), - "Late Summer Bank Holiday" - ) - def get_variable_days(self, year): days = super(UnitedKingdom, self).get_variable_days(year) - days.append(self.get_early_may_bank_holiday(year)) - days.append(self.get_spring_bank_holiday(year)) - days.append(self.get_late_summer_bank_holiday(year)) - # Boxing day & XMas shift - christmas = date(year, 12, 25) - if christmas.weekday() in self.get_weekend_days(): - shift = self.find_following_working_day(christmas) - days.append((shift, "Christmas Shift")) - days.append((shift + timedelta(days=1), "Boxing Day Shift")) + days += [ + Holiday( + date(year, 5, 1) + rd.relativedelta(weekday=rd.MO(1)), + "Early May Bank Holiday", + indication="1st Monday in May", + ), + Holiday( + date(year, 5, 30) + rd.relativedelta(weekday=rd.MO(-1)), + "Spring Bank Holiday", + indication="Last Monday in May", + ), + Holiday( + date(year, 8, 31) + rd.relativedelta(weekday=rd.MO(-1)), + "Late Summer Bank Holiday", + indication="Last Monday in August", + ), + ] return days diff --git a/calendra/tests/test_europe.py b/calendra/tests/test_europe.py index 0ae5db98..d18fb4b5 100644 --- a/calendra/tests/test_europe.py +++ b/calendra/tests/test_europe.py @@ -320,13 +320,13 @@ def test_year_2013(self): def test_shift_2012(self): holidays = self.cal.holidays_set(2012) self.assertIn(date(2012, 1, 1), holidays) # new year day - self.assertIn(date(2012, 1, 2), holidays) # new year day shift def test_shift_2011(self): holidays = self.cal.holidays_set(2011) - self.assertIn(date(2011, 12, 25), holidays) # Christmas it's sunday - self.assertIn(date(2011, 12, 26), holidays) # XMas day shift - self.assertIn(date(2011, 12, 27), holidays) # Boxing day shift + self.assertIn(date(2011, 12, 25), holidays) # XMas day indicated + self.assertIn(date(2011, 12, 26), holidays) # Boxing day + assert self.cal.is_observed_holiday(date(2011, 12, 26)) # XMas observ + assert self.cal.is_observed_holiday(date(2011, 12, 27)) # Boxing observ class UnitedKingdomNorthernIrelandTest(UnitedKingdomTest): From 87b824a8938737a7d7c8ceb28d8fda50b7e2f022 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Nov 2014 17:56:20 -0500 Subject: [PATCH 050/447] Update changelog --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e9cc747c..d356488b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ CHANGELOG ========= +1.1 (2014-11-07) +---------------- + +UnitedKingdom Calendar now uses indicated/observed Holidays. + 1.0 (2014-09-21) ---------------- From 00d8be60a3f21300b6527269f0884d2e40d5fc68 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Nov 2014 18:02:51 -0500 Subject: [PATCH 051/447] Added tag 1.1 for changeset 34730031b5d0 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 0ec09329..e5dba67e 100644 --- a/.hgtags +++ b/.hgtags @@ -1,3 +1,4 @@ e624db136314c527e764b41ec908c37603e9897a 0.2dev-20140827 53be893b2edab43aa759608445e92b517195dc01 0.3-dev-20140827 62b9caa9959662d71f3a5a83a6cd2f39833c3d54 1.0 +34730031b5d093faf63d9b215163ae56b123b38a 1.1 From cf67d33b2bdc65adfc13db795ba171ba413beea4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Nov 2014 18:21:08 -0500 Subject: [PATCH 052/447] Setuptools is required for hgtools support --- setup.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 04fe41e9..2ef9a82e 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,9 @@ from os.path import join, dirname, abspath import sys -PY2 = sys.version_info[0] == 2 - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup # NOQA +import setuptools +PY2 = sys.version_info[0] == 2 def read_relative_file(filename): """Returns contents of the given file, whose path is supposed relative @@ -62,4 +58,4 @@ def read_relative_file(filename): ) if __name__ == '__main__': - setup(**params) + setuptools.setup(**params) From 8ea696c632e271b4c9fd617e8217c3fc0da57893 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Nov 2014 18:21:44 -0500 Subject: [PATCH 053/447] Hgtools 5 is required --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2ef9a82e..6186cded 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ def read_relative_file(filename): 'Programming Language :: Python :: 3.3', ], setup_requires=[ - 'hgtools', + 'hgtools>=5', ], ) From 79f1a35448738a0b790a45490be22926633286e3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Nov 2014 18:26:53 -0500 Subject: [PATCH 054/447] Added tag 1.1.1 for changeset f50a365320e4 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index e5dba67e..f7e78b1a 100644 --- a/.hgtags +++ b/.hgtags @@ -2,3 +2,4 @@ e624db136314c527e764b41ec908c37603e9897a 0.2dev-20140827 53be893b2edab43aa759608445e92b517195dc01 0.3-dev-20140827 62b9caa9959662d71f3a5a83a6cd2f39833c3d54 1.0 34730031b5d093faf63d9b215163ae56b123b38a 1.1 +f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 From 452a58d7c31c74fa01bfbe39981da5eaf9de58a8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Nov 2014 18:28:36 -0500 Subject: [PATCH 055/447] Backed out changeset: 32ec50761e87 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6186cded..70875a7a 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ def read_relative_file(filename): license='MIT License', include_package_data=True, install_requires=REQUIREMENTS, - zip_safe=True, + zip_safe=False, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', From 95f5022e7d3e0b67bc3dc8c8e62d92b61596ff0a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Nov 2014 18:30:11 -0500 Subject: [PATCH 056/447] Update changelog --- CHANGELOG | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 47f4a88b..7e674e61 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,12 @@ CHANGELOG ========= +1.1.2 (2014-11-07) +------------------ + +Fixed issues with packaging (disabled installation an zip egg and now use +setuptools always). + 1.1 (2014-11-07) ---------------- From 093cf279918edc02fab9a0f4503b9b4d56d9ce35 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Nov 2014 18:30:13 -0500 Subject: [PATCH 057/447] Added tag 1.1.2 for changeset 56af90f1e056 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index f7e78b1a..11d48cb6 100644 --- a/.hgtags +++ b/.hgtags @@ -3,3 +3,4 @@ e624db136314c527e764b41ec908c37603e9897a 0.2dev-20140827 62b9caa9959662d71f3a5a83a6cd2f39833c3d54 1.0 34730031b5d093faf63d9b215163ae56b123b38a 1.1 f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 +56af90f1e05681fe8472f5498c7587cfae222300 1.1.2 From 13a184f1e14075e2d95ca2329239ae24308b1a6a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Nov 2014 18:34:51 -0500 Subject: [PATCH 058/447] Fix Flake 8 errors --- calendra/tests/test_europe.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/calendra/tests/test_europe.py b/calendra/tests/test_europe.py index cbf265b7..a20b20a4 100644 --- a/calendra/tests/test_europe.py +++ b/calendra/tests/test_europe.py @@ -325,16 +325,20 @@ def test_shift_2011(self): holidays = self.cal.holidays_set(2011) self.assertIn(date(2011, 12, 25), holidays) # XMas day indicated self.assertIn(date(2011, 12, 26), holidays) # Boxing day - assert self.cal.is_observed_holiday(date(2011, 12, 26)) # XMas observ - assert self.cal.is_observed_holiday(date(2011, 12, 27)) # Boxing observ + # XMas observed + assert self.cal.is_observed_holiday(date(2011, 12, 26)) + # Boxing observed + assert self.cal.is_observed_holiday(date(2011, 12, 27)) def test_shift_2015(self): """ Christmas is on a Friday and Boxing Day on a Saturday. Only Boxing Day should be shifted. """ - assert self.cal.is_observed_holiday(date(2015, 12, 25)) # XMas observ - assert self.cal.is_observed_holiday(date(2015, 12, 28)) # Boxing observ + # XMas observed + assert self.cal.is_observed_holiday(date(2015, 12, 25)) + # Boxing observed + assert self.cal.is_observed_holiday(date(2015, 12, 28)) class UnitedKingdomNorthernIrelandTest(UnitedKingdomTest): From e006f4dd15dea3628e8bfd0e3f48a0372e75e094 Mon Sep 17 00:00:00 2001 From: mikko-ahlroth-vincit Date: Mon, 29 Dec 2014 15:31:23 +0200 Subject: [PATCH 059/447] Rename Finnish independence day Currently the Finnish independence day is named "Labour Day", it should be named "Independence Day" instead. --- calendra/europe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calendra/europe.py b/calendra/europe.py index 23abfc0a..10764235 100644 --- a/calendra/europe.py +++ b/calendra/europe.py @@ -89,7 +89,7 @@ class Finland(WesternCalendar, ChristianMixin): FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( (5, 1, "Labour Day"), - (12, 6, "Labour Day"), + (12, 6, "Independence Day"), ) def get_midsummer_eve(self, year): From e6645a9b8de85225c66fba889a3d312249a75ce3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 29 Dec 2014 09:55:13 -0500 Subject: [PATCH 060/447] Added tag 1.1.3 for changeset 9d252ce92c6f --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 11d48cb6..f2400de8 100644 --- a/.hgtags +++ b/.hgtags @@ -4,3 +4,4 @@ e624db136314c527e764b41ec908c37603e9897a 0.2dev-20140827 34730031b5d093faf63d9b215163ae56b123b38a 1.1 f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 56af90f1e05681fe8472f5498c7587cfae222300 1.1.2 +9d252ce92c6f644442a19cbc19372ca709bd1018 1.1.3 From c1d062056ebc6011bcd3a2e4c1fb5b5adb2cdfcb Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 18:49:09 -0500 Subject: [PATCH 061/447] Add test capturing #4 --- calendra/tests/test_europe.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/calendra/tests/test_europe.py b/calendra/tests/test_europe.py index a20b20a4..52dcfbca 100644 --- a/calendra/tests/test_europe.py +++ b/calendra/tests/test_europe.py @@ -131,6 +131,15 @@ def test_year_2014(self): self.assertIn(date(2014, 6, 21), holidays) # midsummer day self.assertIn(date(2014, 11, 1), holidays) # all saints (special) + def test_holidays_not_shifted(self): + """ + Holidays should not be shifted for Finland. + """ + holidays = self.cal.holidays_set(2014) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2014, 12, 6), observed) + self.assertTrue(self.cal.is_working_day(date(2014, 12, 8))) + class FranceTest(GenericCalendarTest): From 26447a143c22caa44f439de581974ad2b7a1009b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 18:52:25 -0500 Subject: [PATCH 062/447] Allow observance_shift to be None when no shift is expected. --- calendra/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/calendra/core.py b/calendra/core.py index 27750628..e0f71a72 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -148,6 +148,7 @@ def get_observed_date(self, holiday): """ # observance_shift may be overridden in the holiday itself shift = getattr(holiday, 'observance_shift', self.observance_shift) + shift = shift or {} delta = rd.relativedelta(**shift) should_shift = holiday.weekday() in self.get_weekend_days() shifted = holiday + delta if should_shift else holiday From 9273cf98a11d11ffe512b199b28cc371aeedcec5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 18:53:20 -0500 Subject: [PATCH 063/447] Disable observance shift for Finland. Fixes #4. --- calendra/europe.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/calendra/europe.py b/calendra/europe.py index 10764235..50e74554 100644 --- a/calendra/europe.py +++ b/calendra/europe.py @@ -87,6 +87,8 @@ class Finland(WesternCalendar, ChristianMixin): include_boxing_day = True boxing_day_label = "St. Stephen's Day" + observance_shift = None + FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( (5, 1, "Labour Day"), (12, 6, "Independence Day"), From 4bac95090f91fa6bfcf61e9ee98d3e0f9d773bd6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 18:56:50 -0500 Subject: [PATCH 064/447] Update changelog --- CHANGELOG | 12 ++++++++++++ setup.py | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 7e674e61..1349210a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,18 @@ CHANGELOG ========= +1.2 (2015-01-07) +---------------- + +Fixed issue #4 where Finland holidays were shifted but shouldn't have been. +Calendars and Holidays may now specify observance_shift=None to signal no +shift. + +1.1.3 (2014-12-29) +------------------ + +Fix name of Finnish Independence Day. + 1.1.2 (2014-11-07) ------------------ diff --git a/setup.py b/setup.py index 70875a7a..f1fc05fe 100644 --- a/setup.py +++ b/setup.py @@ -54,6 +54,10 @@ def read_relative_file(filename): ], setup_requires=[ 'hgtools>=5', + 'pytest-runner', + ], + tests_require=[ + 'pytest', ], ) From 1b12d3c0228315d2107da4bf8b43b0c200a4c392 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 18:56:52 -0500 Subject: [PATCH 065/447] Added tag 1.2 for changeset 58d445d263c8 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index f2400de8..5940f049 100644 --- a/.hgtags +++ b/.hgtags @@ -5,3 +5,4 @@ e624db136314c527e764b41ec908c37603e9897a 0.2dev-20140827 f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 56af90f1e05681fe8472f5498c7587cfae222300 1.1.2 9d252ce92c6f644442a19cbc19372ca709bd1018 1.1.3 +58d445d263c89344400a6f1c9e3631798262bfee 1.2 From 73ce5f5b1aa15898d5d261b81047890dfe382442 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 18:58:41 -0500 Subject: [PATCH 066/447] Update changelog to describe another change added incidentally. --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1349210a..d1d9ecdb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,9 @@ Fixed issue #4 where Finland holidays were shifted but shouldn't have been. Calendars and Holidays may now specify observance_shift=None to signal no shift. +Package can now be tested with pytest-runner by invoking ``python setup.py +pytest``. + 1.1.3 (2014-12-29) ------------------ From e7a3fa14f2da828cce75bb045a8cf9c2e4bdf925 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 19:01:25 -0500 Subject: [PATCH 067/447] Use pytest to run tests --- .travis.yml | 16 ++++++++-------- tox.ini | 3 +++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index a1f81567..b621e24d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: python -python: 2.7 -env: - - TOX_ENV=py27 - - TOX_ENV=py33 - - TOX_ENV=flake8 -script: tox -e $TOX_ENV -install: - - pip install tox +python: + - 2.7 + - 3.2 + - 3.3 + - 3.4 + - pypy +script: + - python setup.py pytest diff --git a/tox.ini b/tox.ini index f5b5f655..53ccf463 100644 --- a/tox.ini +++ b/tox.ini @@ -16,3 +16,6 @@ deps = flake8 commands = flake8 calendra + +[pytest] +norecursedirs=build .eggs From f11e2c0f724ff3c49aec9b04a1ef0f5810a7ae3e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 19:05:29 -0500 Subject: [PATCH 068/447] Remove other tox config --- tox.ini | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/tox.ini b/tox.ini index 53ccf463..50e453b8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,21 +1,2 @@ -[tox] -envlist = py27,flake8,py33 - -[testenv] -deps = - nose - coverage - -commands = - python setup.py develop - nosetests -sv --with-coverage --cover-package=calendra calendra - pip freeze - -[testenv:flake8] -deps = - flake8 - -commands = flake8 calendra - [pytest] norecursedirs=build .eggs From 4abdb605b44650325c824b388caac75300faa451 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 19:11:09 -0500 Subject: [PATCH 069/447] Restore coverage support. Bump pytest-runner. --- setup.py | 3 ++- tox.ini | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f1fc05fe..00ddef47 100644 --- a/setup.py +++ b/setup.py @@ -54,10 +54,11 @@ def read_relative_file(filename): ], setup_requires=[ 'hgtools>=5', - 'pytest-runner', + 'pytest-runner>=2.2.1', ], tests_require=[ 'pytest', + 'pytest-cov', ], ) diff --git a/tox.ini b/tox.ini index 50e453b8..ae688eae 100644 --- a/tox.ini +++ b/tox.ini @@ -1,2 +1,3 @@ [pytest] norecursedirs=build .eggs +addopts=--cov calendra From e5dff6ba1884613efd70f87cb79df426f5a4411f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 19:16:37 -0500 Subject: [PATCH 070/447] Restore pep8 checking --- setup.py | 1 + tox.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 00ddef47..b426f130 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ def read_relative_file(filename): tests_require=[ 'pytest', 'pytest-cov', + 'pytest-pep8', ], ) diff --git a/tox.ini b/tox.ini index ae688eae..2866224e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,3 +1,3 @@ [pytest] norecursedirs=build .eggs -addopts=--cov calendra +addopts=--cov calendra --pep8 From d05de8681b391ef371a3adb34242b3a64447e059 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 19:17:12 -0500 Subject: [PATCH 071/447] Fix flake errors in setup.py --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b426f130..a09ff776 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- import io from os.path import join, dirname, abspath import sys @@ -8,6 +8,7 @@ PY2 = sys.version_info[0] == 2 + def read_relative_file(filename): """Returns contents of the given file, whose path is supposed relative to this module.""" From 4eb4cc16359cd355ce2110924420e8e993f83470 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 20:11:50 -0500 Subject: [PATCH 072/447] Also ignore *.egg for systems with older setuptools. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 2866224e..86c6d567 100644 --- a/tox.ini +++ b/tox.ini @@ -1,3 +1,3 @@ [pytest] -norecursedirs=build .eggs +norecursedirs=build .eggs *.egg addopts=--cov calendra --pep8 From da256bdd123974d1774f78a9b21d914d9fb21b1b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 20:14:56 -0500 Subject: [PATCH 073/447] Disable pep8 as it appears to be causing issues on travis. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 86c6d567..58bc8831 100644 --- a/tox.ini +++ b/tox.ini @@ -1,3 +1,3 @@ [pytest] norecursedirs=build .eggs *.egg -addopts=--cov calendra --pep8 +addopts=--cov calendra From fe525efc77d1046f1d908724b4aeec322b31b31e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Jan 2015 20:23:32 -0500 Subject: [PATCH 074/447] Python 3.2 and pypy tests fail so aren't supported. --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b621e24d..47b8f4ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,7 @@ language: python python: - 2.7 - - 3.2 - 3.3 - 3.4 - - pypy script: - python setup.py pytest From e95bccd2e877457bdec83dc8bb125b4694655673 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 15 Feb 2015 11:19:12 -0500 Subject: [PATCH 075/447] Correct usage artifact from worklandar. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index be273fe7..6ae30a4d 100644 --- a/README.rst +++ b/README.rst @@ -29,7 +29,7 @@ Usage sample .. code-block:: python >>> from datetime import date - >>> from workalendar.europe import France + >>> from calendra.europe import France >>> cal = France() >>> cal.holidays(2012) [(datetime.date(2012, 1, 1), 'New year'), From 1242047a5173cc4b4a1cc82362f4296cbdf985b6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 15 Feb 2015 11:19:50 -0500 Subject: [PATCH 076/447] Update changelog --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index d1d9ecdb..3161d5c0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ CHANGELOG ========= +1.2.1 (2015-02-15) +------------------ + +Correct usage in example. + 1.2 (2015-01-07) ---------------- From 965637236d598ecc162584901dfc735d014814a8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 15 Feb 2015 11:21:06 -0500 Subject: [PATCH 077/447] Add aliases --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..52b7872a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[aliases] +release=test sdist upload +test=pytest From b1cbbf043067d622f21612e21ba66f18f321f2a5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 15 Feb 2015 11:21:17 -0500 Subject: [PATCH 078/447] Added tag 1.2.1 for changeset 5e89a1ee4d09 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 5940f049..3629c6ef 100644 --- a/.hgtags +++ b/.hgtags @@ -6,3 +6,4 @@ f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 56af90f1e05681fe8472f5498c7587cfae222300 1.1.2 9d252ce92c6f644442a19cbc19372ca709bd1018 1.1.3 58d445d263c89344400a6f1c9e3631798262bfee 1.2 +5e89a1ee4d09a1b2000108ad4bb1f7d80ee87449 1.2.1 From e4ec77c6f1d511512f8bcd95107534c4e318e802 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 15 Feb 2015 11:30:36 -0500 Subject: [PATCH 079/447] Set release date --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 0b00ef51..7f6a956a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ CHANGELOG ========= -1.3 (unreleased) +1.3 (2015-02-15) ---------------- Incorporate these fixes from Workalendar 0.3: From 265f11175bb396fd323677619cf4fa6a6d4138e3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 15 Feb 2015 11:31:16 -0500 Subject: [PATCH 080/447] Add .cache to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 651ba512..61fc0740 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ *.egg-info dist/ build/ +.cache/ .coverage .tox/ From 741f2108ada154ef9b3b10f4653177b1c3e2b41f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 15 Feb 2015 11:31:22 -0500 Subject: [PATCH 081/447] Added tag 1.3 for changeset 774242ccc460 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 3629c6ef..1d017e14 100644 --- a/.hgtags +++ b/.hgtags @@ -7,3 +7,4 @@ f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 9d252ce92c6f644442a19cbc19372ca709bd1018 1.1.3 58d445d263c89344400a6f1c9e3631798262bfee 1.2 5e89a1ee4d09a1b2000108ad4bb1f7d80ee87449 1.2.1 +774242ccc46081f5b6fd205bf650ff90c9e75e83 1.3 From 20fb295c10361818b6db9cfce2caf557c90e9633 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 14 Apr 2015 17:30:04 -0400 Subject: [PATCH 082/447] Add test capturing #5 --- calendra/tests/test_usa.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/calendra/tests/test_usa.py b/calendra/tests/test_usa.py index f2b78c16..66a2a4fb 100644 --- a/calendra/tests/test_usa.py +++ b/calendra/tests/test_usa.py @@ -31,6 +31,14 @@ def test_year_2013(self): self.assertIn(date(2013, 10, 14), holidays) # Colombus self.assertIn(date(2013, 11, 28), holidays) # Thanskgiving + def test_independence_day_nearest_weekday(self): + """ + Independence Day should shift to the nearest weekday. + """ + holidays = self.cal.holidays_set(2015) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2015, 7, 3), observed) + def test_presidential_year(self): self.assertTrue(UnitedStates.is_presidential_year(2012)) self.assertFalse(UnitedStates.is_presidential_year(2013)) From 0ac530519f02869266750ad51b636ff1923e75af Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 14 Apr 2015 18:09:31 -0400 Subject: [PATCH 083/447] Add support for observation shift to nearest weekday. Fixes #5. --- CHANGELOG | 10 ++++++++++ calendra/core.py | 20 ++++++++++++++++++++ calendra/usa.py | 7 ++++++- setup.py | 1 + 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 7f6a956a..21aa4794 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,16 @@ CHANGELOG ========= +1.4 (2015-04-14) +---------------- + +``Calendar.get_observed_date`` now allows ``observance_shift`` to be +a callable accepting the holiday and calendar and returning the observed +date. ``Holiday`` supplies a ``get_nearest_weekday`` method suitable for +locating the nearest weekday. + +- #5: USA Independence Day now honors the nearest weekday model. + 1.3 (2015-02-15) ---------------- diff --git a/calendra/core.py b/calendra/core.py index feb9f63d..35c3216f 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -3,6 +3,7 @@ import warnings import ephem import pytz +import itertools from calendar import monthrange from datetime import date, timedelta, datetime @@ -12,6 +13,7 @@ from lunardate import LunarDate from calverter import Calverter from dateutil import relativedelta as rd +from more_itertools import recipes MON, TUE, WED, THU, FRI, SAT, SUN = range(7) @@ -65,6 +67,22 @@ def replace(self, *args, **kwargs): vars(replaced).update(vars(self)) return replaced + def nearest_weekday(self, calendar): + """ + Return the nearest weekday to self. + """ + weekend_days = calendar.get_weekend_days() + deltas = (timedelta(n) for n in itertools.count()) + candidates = recipes.flatten( + (self - delta, self + delta) + for delta in deltas + ) + matches = ( + day for day in candidates + if day.weekday() not in weekend_days + ) + return next(matches) + @classmethod def _from_fixed_definition(cls, item): """For backward compatibility, load Holiday object from an item of @@ -148,6 +166,8 @@ def get_observed_date(self, holiday): """ # observance_shift may be overridden in the holiday itself shift = getattr(holiday, 'observance_shift', self.observance_shift) + if callable(shift): + return shift(holiday, self) shift = shift or {} delta = rd.relativedelta(**shift) should_shift = holiday.weekday() in self.get_weekend_days() diff --git a/calendra/usa.py b/calendra/usa.py index e4cf1e64..108d23b4 100644 --- a/calendra/usa.py +++ b/calendra/usa.py @@ -13,7 +13,12 @@ class UnitedStates(WesternCalendar, ChristianMixin): "United States of America" FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( - Holiday(date(2000, 7, 4), 'Independence Day', indication='July 4'), + Holiday( + date(2000, 7, 4), + 'Independence Day', + indication='July 4', + observance_shift=Holiday.nearest_weekday, + ), Holiday(date(2000, 11, 11), 'Veterans Day', indication='Nov 11'), ) diff --git a/setup.py b/setup.py index a09ff776..2d641fd3 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ def read_relative_file(filename): 'lunardate', 'pytz', 'pyCalverter', + 'more_itertools', ] if PY2: From bf017f73fdcf073e664f3b468eb503a5d9fecc35 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 14 Apr 2015 18:11:26 -0400 Subject: [PATCH 084/447] Expand test to capture the main cases --- calendra/tests/test_usa.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/calendra/tests/test_usa.py b/calendra/tests/test_usa.py index 66a2a4fb..513f220e 100644 --- a/calendra/tests/test_usa.py +++ b/calendra/tests/test_usa.py @@ -35,6 +35,14 @@ def test_independence_day_nearest_weekday(self): """ Independence Day should shift to the nearest weekday. """ + holidays = self.cal.holidays_set(2010) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2010, 7, 5), observed) + + holidays = self.cal.holidays_set(2011) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2011, 7, 4), observed) + holidays = self.cal.holidays_set(2015) observed = set(map(self.cal.get_observed_date, holidays)) self.assertIn(date(2015, 7, 3), observed) From 2037a6ed5226276576475ad4eff81f0ccd129d16 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 14 Apr 2015 18:14:30 -0400 Subject: [PATCH 085/447] Added tag 1.4 for changeset 58d4d242bca2 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 1d017e14..7f15a7ea 100644 --- a/.hgtags +++ b/.hgtags @@ -8,3 +8,4 @@ f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 58d445d263c89344400a6f1c9e3631798262bfee 1.2 5e89a1ee4d09a1b2000108ad4bb1f7d80ee87449 1.2.1 774242ccc46081f5b6fd205bf650ff90c9e75e83 1.3 +58d4d242bca28593caf806adc14bea1c86c65434 1.4 From 535c458f4e262265f37bd5527e68cb7fed7ef166 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 14 Apr 2015 18:47:02 -0400 Subject: [PATCH 086/447] The ephem package should now work for either Python. --- setup.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 2d641fd3..ef9927d8 100644 --- a/setup.py +++ b/setup.py @@ -24,13 +24,9 @@ def read_relative_file(filename): 'pytz', 'pyCalverter', 'more_itertools', + 'ephem', ] -if PY2: - REQUIREMENTS.append('pyephem') -else: - REQUIREMENTS.append('ephem') - params = dict( name=NAME, description=DESCRIPTION, From 5ad18328e8f38abcc5a0fc4a549e7a3c40697d4e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 19 Dec 2015 22:29:56 -0500 Subject: [PATCH 087/447] Generate project skeleton --- .hgignore | 2 ++ .travis.yml | 8 +++++++ CHANGES.rst | 0 README.rst | 2 ++ docs/conf.py | 19 +++++++++++++++++ docs/history.rst | 8 +++++++ docs/index.rst | 22 +++++++++++++++++++ pytest.ini | 4 ++++ setup.cfg | 6 ++++++ setup.py | 51 ++++++++++++++++++++++++++++++++++++++++++++ skeleton/__init__.py | 0 11 files changed, 122 insertions(+) create mode 100644 .hgignore create mode 100644 .travis.yml create mode 100644 CHANGES.rst create mode 100644 README.rst create mode 100644 docs/conf.py create mode 100644 docs/history.rst create mode 100644 docs/index.rst create mode 100644 pytest.ini create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 skeleton/__init__.py diff --git a/.hgignore b/.hgignore new file mode 100644 index 00000000..9d0b71a3 --- /dev/null +++ b/.hgignore @@ -0,0 +1,2 @@ +build +dist diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..6e5e969d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +sudo: false +language: python +python: + - 2.7 + - 3.5 +script: + - pip install -U pytest + - python setup.py test diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 00000000..e69de29b diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..217a0758 --- /dev/null +++ b/README.rst @@ -0,0 +1,2 @@ +skeleton +======== diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..c8348485 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import setuptools_scm + +extensions = [ + 'sphinx.ext.autodoc', +] + +# General information about the project. +project = 'skeleton' +copyright = '2015 Jason R. Coombs' + +# The short X.Y version. +version = setuptools_scm.get_version(root='..', relative_to=__file__) +# The full version, including alpha/beta/rc tags. +release = version + +master_doc = 'index' diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 00000000..907000bf --- /dev/null +++ b/docs/history.rst @@ -0,0 +1,8 @@ +:tocdepth: 2 + +.. _changes: + +History +******* + +.. include:: ../CHANGES.rst diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..d14131b0 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,22 @@ +Welcome to skeleton documentation! +======================================== + +.. toctree:: + :maxdepth: 1 + + history + + +.. automodule:: skeleton + :members: + :undoc-members: + :show-inheritance: + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..9752c365 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +norecursedirs=*.egg .eggs dist build +addopts=--doctest-modules +doctest_optionflags=ALLOW_UNICODE ELLIPSIS diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..445263a6 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,6 @@ +[aliases] +release = sdist bdist_wheel build_sphinx upload upload_docs +test = pytest + +[wheel] +universal = 1 diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..e6edf34c --- /dev/null +++ b/setup.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# Generated by jaraco.develop 2.27.1 +# https://pypi.python.org/pypi/jaraco.develop + +import io +import sys + +import setuptools + +with io.open('README.rst', encoding='utf-8') as readme: + long_description = readme.read() + +needs_pytest = {'pytest', 'test'}.intersection(sys.argv) +pytest_runner = ['pytest_runner'] if needs_pytest else [] +needs_sphinx = {'release', 'build_sphinx', 'upload_docs'}.intersection(sys.argv) +sphinx = ['sphinx'] if needs_sphinx else [] +needs_wheel = {'release', 'bdist_wheel'}.intersection(sys.argv) +wheel = ['wheel'] if needs_wheel else [] + +setup_params = dict( + name='skeleton', + use_scm_version=True, + author="Jason R. Coombs", + author_email="jaraco@jaraco.com", + description="skeleton", + long_description=long_description, + url="https://github.com/jaraco/skeleton", + packages=setuptools.find_packages(), + include_package_data=True, + install_requires=[ + ], + extras_require={ + }, + setup_requires=[ + 'setuptools_scm>=1.9', + ] + pytest_runner + sphinx + wheel, + tests_require=[ + 'pytest>=2.8', + ], + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + ], + entry_points={ + }, +) +if __name__ == '__main__': + setuptools.setup(**setup_params) diff --git a/skeleton/__init__.py b/skeleton/__init__.py new file mode 100644 index 00000000..e69de29b From 52df9f0ff993963ad519495ec882513d4018dc8f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 26 Dec 2015 08:46:57 -0500 Subject: [PATCH 088/447] Remove the package from the skeleton. It has no value. --- skeleton/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 skeleton/__init__.py diff --git a/skeleton/__init__.py b/skeleton/__init__.py deleted file mode 100644 index e69de29b..00000000 From 5782d0536efe0d5c516ed9badeba1947208a22b0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Jan 2016 08:30:19 -0500 Subject: [PATCH 089/447] Add gitignore. Make .hgignore empty - there's nothing here that's project specific. --- .gitignore | 0 .hgignore | 2 -- 2 files changed, 2 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/.hgignore b/.hgignore index 9d0b71a3..e69de29b 100644 --- a/.hgignore +++ b/.hgignore @@ -1,2 +0,0 @@ -build -dist From 8277707492656a3ade588e8826c02a11c3cad191 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Jan 2016 08:35:39 -0500 Subject: [PATCH 090/447] Upon further reading, hg-git supports .gitignore, so omit .hgignore. --- .hgignore | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .hgignore diff --git a/.hgignore b/.hgignore deleted file mode 100644 index e69de29b..00000000 From 6d571b47f84e8ee87decbaffbd577c5f556efb4b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Jan 2016 09:03:11 -0500 Subject: [PATCH 091/447] Update copyright --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index c8348485..18740743 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,7 +9,7 @@ # General information about the project. project = 'skeleton' -copyright = '2015 Jason R. Coombs' +copyright = '2016 Jason R. Coombs' # The short X.Y version. version = setuptools_scm.get_version(root='..', relative_to=__file__) From b5982a5472720f001fe52f0defc2c2d804d3501e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 15 Feb 2016 04:59:44 -0500 Subject: [PATCH 092/447] Learning from lessons in the keyring 8.4 release (https://github.com/jaraco/keyring/issues/210), always clean the build artifacts before cutting a release. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 445263a6..8004dcb6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [aliases] -release = sdist bdist_wheel build_sphinx upload upload_docs +release = clean --all sdist bdist_wheel build_sphinx upload upload_docs test = pytest [wheel] From 65b649869bb4f0ab1aad5deb3a30973a45082d4a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 5 Mar 2016 08:56:02 -0500 Subject: [PATCH 093/447] Derive description, url, and namespace_packages from name --- setup.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index e6edf34c..e825a5d7 100644 --- a/setup.py +++ b/setup.py @@ -17,16 +17,20 @@ needs_wheel = {'release', 'bdist_wheel'}.intersection(sys.argv) wheel = ['wheel'] if needs_wheel else [] +name = 'skeleton' +description = '' + setup_params = dict( - name='skeleton', + name=name, use_scm_version=True, author="Jason R. Coombs", author_email="jaraco@jaraco.com", - description="skeleton", + description=description or name, long_description=long_description, - url="https://github.com/jaraco/skeleton", + url="https://github.com/jaraco/" + name, packages=setuptools.find_packages(), include_package_data=True, + namespace_packages=name.split('.')[:-1], install_requires=[ ], extras_require={ From 7f59d104ee53ac234f40cd24108a2a6a1c342665 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 14 Mar 2016 09:56:23 -0400 Subject: [PATCH 094/447] Also test under Python 3.5 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 47b8f4ed..b0230663 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,6 @@ python: - 2.7 - 3.3 - 3.4 + - 3.5 script: - python setup.py pytest From 4ce3934a94ac83828fb06f09a7bc60274ad8162d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 14 Mar 2016 09:57:49 -0400 Subject: [PATCH 095/447] Execute tests with setup.py test --- .travis.yml | 2 +- setup.cfg | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b0230663..ff872847 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ python: - 3.4 - 3.5 script: - - python setup.py pytest + - python setup.py test diff --git a/setup.cfg b/setup.cfg index 52b7872a..6bbf0c09 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [aliases] -release=test sdist upload -test=pytest +release = test sdist upload +test = pytest From 1b19891d8cf32967ec36cca1ec3c09f1df3d9aa5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 14 Mar 2016 09:58:27 -0400 Subject: [PATCH 096/447] Prefer setuptools_scm to hgtools --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6130bb31..07d597b8 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ def read_relative_file(filename): name=NAME, description=DESCRIPTION, packages=['calendra'], - use_vcs_version=True, + use_scm_version=True, long_description=read_relative_file('README.rst'), author='Bruno Bord', author_email='bruno.bord@people-doc.com', @@ -51,7 +51,7 @@ def read_relative_file(filename): 'Programming Language :: Python :: 3.3', ], setup_requires=[ - 'hgtools>=5', + 'setuptools_scm', 'pytest-runner>=2.2.1', ], tests_require=[ From ec47f69dd225ce869f88d9aa49bafafa627c4820 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 14 Mar 2016 10:05:08 -0400 Subject: [PATCH 097/447] Restore attribution, lost in merge. --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 07d597b8..8a45a191 100644 --- a/setup.py +++ b/setup.py @@ -33,9 +33,9 @@ def read_relative_file(filename): packages=['calendra'], use_scm_version=True, long_description=read_relative_file('README.rst'), - author='Bruno Bord', - author_email='bruno.bord@people-doc.com', - url='https://github.com/novafloss/workalendar', + author='Jason R. Coombs', + author_email='jaraco@jaraco.com', + url='https://github.com/jaraco/calendra', license='MIT License', include_package_data=True, install_requires=REQUIREMENTS, From 819feaa412326e6412ec41ec39d6724cdd234ec6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 14 Mar 2016 10:05:28 -0400 Subject: [PATCH 098/447] Advertise support for Python 3.4 and 3.5. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 8a45a191..6ed1dc2b 100644 --- a/setup.py +++ b/setup.py @@ -49,6 +49,8 @@ def read_relative_file(filename): 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ], setup_requires=[ 'setuptools_scm', From f9a545d0a0ff076d949a2df68a7687d805a174b4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 14 Mar 2016 10:06:41 -0400 Subject: [PATCH 099/447] Added tag 1.5 for changeset 0a5dea7f7adb --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 7f15a7ea..e34ccf32 100644 --- a/.hgtags +++ b/.hgtags @@ -9,3 +9,4 @@ f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 5e89a1ee4d09a1b2000108ad4bb1f7d80ee87449 1.2.1 774242ccc46081f5b6fd205bf650ff90c9e75e83 1.3 58d4d242bca28593caf806adc14bea1c86c65434 1.4 +0a5dea7f7adbe859fd963b864ae0e969ac3e0fff 1.5 From 752b1096b8d126df040847c19c82aa042ccfe77a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 2 Apr 2016 21:46:39 -0400 Subject: [PATCH 100/447] Add PyPI deployment --- .travis.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6e5e969d..a5b29eeb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,18 @@ sudo: false language: python python: - - 2.7 - - 3.5 +- 2.7 +- 3.5 script: - - pip install -U pytest - - python setup.py test +- pip install -U pytest +- python setup.py test +deploy: + provider: pypi + on: + tags: true + all_branches: true + user: jaraco + provider: pypi + # supply password with `travis encrypt --add deploy.password` + distributions: release + python: 3.5 From 58f71d1e2bad2392cde6cfabef4fc9cfc8bfec28 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 2 Apr 2016 22:03:05 -0400 Subject: [PATCH 101/447] Remove duplicate provider line --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a5b29eeb..7ff32e91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,6 @@ deploy: tags: true all_branches: true user: jaraco - provider: pypi # supply password with `travis encrypt --add deploy.password` distributions: release python: 3.5 From 01e953bac9ac132fba90550492ee9f7eedfce7e0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 2 Apr 2016 22:23:15 -0400 Subject: [PATCH 102/447] Add support for linking to issues and adding datestamps to changelog entries. --- docs/conf.py | 20 ++++++++++++++++++++ docs/history.rst | 2 +- setup.py | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 18740743..9c7ad1b0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,6 +5,7 @@ extensions = [ 'sphinx.ext.autodoc', + 'rst.linker', ] # General information about the project. @@ -17,3 +18,22 @@ release = version master_doc = 'index' + +link_files = { + 'CHANGES.rst': dict( + using=dict( + GH='https://github.com', + project=project, + ), + replace=[ + dict( + pattern=r"(Issue )?#(?P\d+)", + url='{GH}/jaraco/{project}/issues/{issue}', + ), + dict( + pattern=r"^(?m)((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n", + with_scm="{text}\n{rev[timestamp]:%d %b %Y}\n", + ), + ], + ), +} diff --git a/docs/history.rst b/docs/history.rst index 907000bf..8e217503 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -5,4 +5,4 @@ History ******* -.. include:: ../CHANGES.rst +.. include:: ../CHANGES (links).rst diff --git a/setup.py b/setup.py index e825a5d7..99d6c9c9 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ needs_pytest = {'pytest', 'test'}.intersection(sys.argv) pytest_runner = ['pytest_runner'] if needs_pytest else [] needs_sphinx = {'release', 'build_sphinx', 'upload_docs'}.intersection(sys.argv) -sphinx = ['sphinx'] if needs_sphinx else [] +sphinx = ['sphinx', 'rst.linker'] if needs_sphinx else [] needs_wheel = {'release', 'bdist_wheel'}.intersection(sys.argv) wheel = ['wheel'] if needs_wheel else [] From 04528bdf294e0b9eb4920d4b4a8637b6871b1606 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 16 Apr 2016 15:22:52 +0100 Subject: [PATCH 103/447] Move Python 3.5 condition to 'on' section --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7ff32e91..c5f3495b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ deploy: on: tags: true all_branches: true + python: 3.5 user: jaraco # supply password with `travis encrypt --add deploy.password` distributions: release - python: 3.5 From 29d9ebee0154e77e416162061752833410e98cbd Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 29 Apr 2016 09:32:33 -0400 Subject: [PATCH 104/447] Update comment to reflect the Github-backed skeleton model (preferred to the generation library-backed model). --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 99d6c9c9..91e4110c 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Generated by jaraco.develop 2.27.1 -# https://pypi.python.org/pypi/jaraco.develop + +# Project skeleton maintained at https://github.com/jaraco/skeleton import io import sys From b93b3a0348e9a17ec323f74eb9eb0ec8e82367ff Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 24 May 2016 12:21:58 -0400 Subject: [PATCH 105/447] Exclude the skeleton branch from testing --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index c5f3495b..bb6d47e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,9 @@ python: script: - pip install -U pytest - python setup.py test +branches: + except: + - skeleton deploy: provider: pypi on: From 3da75ae120bd363a616adba9f0db97a94a71dc2b Mon Sep 17 00:00:00 2001 From: Fredrik Stockman Date: Tue, 14 Jun 2016 15:29:17 +0200 Subject: [PATCH 106/447] No observance shift for Sweden There is no observance observance shift in Sweden. I.e. the following is currently wrong: ``` from calendra.europe import Sweden from datetime import date > cal = Sweden() > christmas_eve = date(2015,12,24) > cal.add_working_days(christmas_eve, 1) < datetime.date(2015, 12, 29) # should be December 28 > cal.is_observed_holiday(date(2015,12,28)) < True ``` Setting `observance_shift = None` for `Sweden` fixes this issue. --- calendra/europe.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/calendra/europe.py b/calendra/europe.py index f62470ee..7c514176 100644 --- a/calendra/europe.py +++ b/calendra/europe.py @@ -93,6 +93,8 @@ class Sweden(WesternCalendar, ChristianMixin): include_christmas_eve = True include_boxing_day = True boxing_day_label = "Second Day of Christmas" + + observance_shift = None FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( (5, 1, "Labour Day"), From 47076292150d07f520f48d34ee8a15e20a0cee28 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 15 Jun 2016 07:55:07 +0200 Subject: [PATCH 107/447] Update changelog. Ref #6. --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 93e97f21..95c6322f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ CHANGELOG ========= +1.5.1 +----- + +- #6: Remove observance shift for Sweden. + 1.5 --- From b68b5594ea43b66203f809609a0c634d1bbf904f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 15 Jun 2016 08:00:58 +0200 Subject: [PATCH 108/447] Rename CHANGES.rst to match jaraco skeleton. --- CHANGELOG => CHANGES.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CHANGELOG => CHANGES.rst (100%) diff --git a/CHANGELOG b/CHANGES.rst similarity index 100% rename from CHANGELOG rename to CHANGES.rst From 6236c425ad37bf717dc4e19e02f2f0cd8150ec95 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 16 Jun 2016 16:27:32 +0200 Subject: [PATCH 109/447] Update changelog --- CHANGES.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 95c6322f..6851f138 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,10 +1,13 @@ CHANGELOG ========= -1.5.1 ------ +1.6 +--- - #6: Remove observance shift for Sweden. +- Use `jaraco skeleton `_ to + maintain the project structure, adding automatic releases + from continuous integration and bundled documentation. 1.5 --- From 0ed5c2b189b03caecc324b61e5718034d21118b8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 16 Jun 2016 18:23:43 +0200 Subject: [PATCH 110/447] Add password for PyPI integration. --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ad231db6..7a0d3f9d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ script: - python setup.py test branches: except: - - skeleton + - skeleton deploy: provider: pypi on: @@ -18,5 +18,6 @@ deploy: all_branches: true python: 3.5 user: jaraco - # supply password with `travis encrypt --add deploy.password` distributions: release + password: + secure: lq5T3nLudhmOP8mKbAh7QsyXjhRUi+zbACi6M8lPCj1+XPj+6uUdMaq3A/fg0+EivGffw8PKceXeMlHboQI0hD9Bt4ISmeixlDi7wNcOSP/TWks2VFr9Gq+nI85LjLwZ9jyLh9CZ1C8gedWy0DfarnDSZ9/5k6Ts39zK0yKcQLg= From 8ea8db0a7242bafae6d37e6ebdb28df832ed5fcc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 16 Jun 2016 19:38:28 +0200 Subject: [PATCH 111/447] Update docs to reference actual module. --- docs/index.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index d14131b0..9645ff5a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,5 @@ -Welcome to skeleton documentation! -======================================== +Welcome to calendra documentation! +================================== .. toctree:: :maxdepth: 1 @@ -7,7 +7,7 @@ Welcome to skeleton documentation! history -.. automodule:: skeleton +.. automodule:: calendra :members: :undoc-members: :show-inheritance: From 0024765a6d92123bf21c8c90ef3cef28c4f7ce6a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 16 Jun 2016 19:38:33 +0200 Subject: [PATCH 112/447] Added tag 1.6 for changeset a1e714866c1e --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index e34ccf32..840ab4cd 100644 --- a/.hgtags +++ b/.hgtags @@ -10,3 +10,4 @@ f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 774242ccc46081f5b6fd205bf650ff90c9e75e83 1.3 58d4d242bca28593caf806adc14bea1c86c65434 1.4 0a5dea7f7adbe859fd963b864ae0e969ac3e0fff 1.5 +a1e714866c1e665caa834e812960293a55376d39 1.6 From 03e17651b00a1fb74b467a12eac1e3a5b79cef80 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 17 Jun 2016 00:02:12 +0200 Subject: [PATCH 113/447] Added tag 1.7 for changeset f9d369af37c9 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 840ab4cd..b634a7e8 100644 --- a/.hgtags +++ b/.hgtags @@ -11,3 +11,4 @@ f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 58d4d242bca28593caf806adc14bea1c86c65434 1.4 0a5dea7f7adbe859fd963b864ae0e969ac3e0fff 1.5 a1e714866c1e665caa834e812960293a55376d39 1.6 +f9d369af37c9114940a9179d4707f60df62ae28b 1.7 From 4da0f85ed3031d9dd5d78d9723d707223fdb8f6f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 17 Jun 2016 00:12:27 +0200 Subject: [PATCH 114/447] Combine pytest.ini with tox.ini --- pytest.ini | 4 ---- tox.ini | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 9752c365..00000000 --- a/pytest.ini +++ /dev/null @@ -1,4 +0,0 @@ -[pytest] -norecursedirs=*.egg .eggs dist build -addopts=--doctest-modules -doctest_optionflags=ALLOW_UNICODE ELLIPSIS diff --git a/tox.ini b/tox.ini index 58bc8831..f7ffc0fc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,3 +1,4 @@ [pytest] norecursedirs=build .eggs *.egg -addopts=--cov calendra +addopts=--cov calendra --doctest-modules +doctest_optionflags=ALLOW_UNICODE ELLIPSIS From efa552e7ee31d0fb1dab3d1a2986cad0834b04f6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 3 Aug 2016 09:51:36 -0400 Subject: [PATCH 115/447] Add badges for PyPI, downloads, and Travis-CI. --- README.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.rst b/README.rst index 217a0758..e739bf50 100644 --- a/README.rst +++ b/README.rst @@ -1,2 +1,11 @@ skeleton ======== + +.. image:: https://badge.fury.io/py/skeleton.svg + :target: https://badge.fury.io/py/skeleton + +.. image:: https://pypip.in/d/skeleton/badge.png + :target: https://crate.io/packages/skeleton/ + +.. image:: https://secure.travis-ci.org/jaraco/skeleton.png + :target: http://travis-ci.org/jaraco/skeleton From e2900e901e9c24eb7ebf59792dc198bf0bd27cc8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 3 Aug 2016 09:54:22 -0400 Subject: [PATCH 116/447] Change indentation to match that which the travis tool generates when adding the password. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bb6d47e0..4abbe308 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ script: - python setup.py test branches: except: - - skeleton + - skeleton deploy: provider: pypi on: From c8c034e68873e40ed55f0b9f04afc5949eb54727 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 3 Aug 2016 10:07:15 -0400 Subject: [PATCH 117/447] Use shields.io, as some of these other providers seem to have gone out of business. --- README.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index e739bf50..33249644 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,10 @@ skeleton ======== -.. image:: https://badge.fury.io/py/skeleton.svg - :target: https://badge.fury.io/py/skeleton +.. image:: https://img.shields.io/pypi/v/skeleton.svg + :target: https://pypi.io/project/skeleton -.. image:: https://pypip.in/d/skeleton/badge.png - :target: https://crate.io/packages/skeleton/ +.. image:: https://img.shields.io/pypi/dm/skeleton.svg -.. image:: https://secure.travis-ci.org/jaraco/skeleton.png - :target: http://travis-ci.org/jaraco/skeleton +.. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg + :target: http://travis-ci.org/jaraco/skeleton From 3f61a73b657a7a845f0f7fdbcebbf92c7f8e6c22 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 3 Aug 2016 10:13:55 -0400 Subject: [PATCH 118/447] Also add pyversions --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 33249644..db95581e 100644 --- a/README.rst +++ b/README.rst @@ -4,6 +4,8 @@ skeleton .. image:: https://img.shields.io/pypi/v/skeleton.svg :target: https://pypi.io/project/skeleton +.. image:: https://img.shields.io/pypi/pyversions/skeleton.svg + .. image:: https://img.shields.io/pypi/dm/skeleton.svg .. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg From dfb1a9424d373fb2f949f2d45f79d8008ede276b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 8 Aug 2016 14:49:32 -0400 Subject: [PATCH 119/447] Path is now .org --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index db95581e..75c0b4f0 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ skeleton ======== .. image:: https://img.shields.io/pypi/v/skeleton.svg - :target: https://pypi.io/project/skeleton + :target: https://pypi.org/project/skeleton .. image:: https://img.shields.io/pypi/pyversions/skeleton.svg From 7edaa321dead30e33accdb7512f9e95bbef9fe38 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 9 Aug 2016 09:50:49 -0400 Subject: [PATCH 120/447] Update release process to use warehouse rather than legacy PyPI. Ref pypa/warehouse#1422. --- .travis.yml | 3 ++- setup.cfg | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4abbe308..9f4c5178 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,10 +11,11 @@ branches: - skeleton deploy: provider: pypi + server: https://upload.pypi.org/legacy/ on: tags: true all_branches: true python: 3.5 user: jaraco # supply password with `travis encrypt --add deploy.password` - distributions: release + distributions: dists diff --git a/setup.cfg b/setup.cfg index 8004dcb6..dcd8d122 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,10 @@ [aliases] -release = clean --all sdist bdist_wheel build_sphinx upload upload_docs +release = dists build_sphinx upload upload_docs +dists = clean --all sdist bdist_wheel test = pytest [wheel] universal = 1 + +[upload] +repository = https://upload.pypi.org/legacy/ From d024388cac7d3804c763e6f5656e75a6bde7d33c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 15 Aug 2016 09:51:41 -0400 Subject: [PATCH 121/447] The name of the project need not be in the README --- README.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.rst b/README.rst index 75c0b4f0..1c5d10ec 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,3 @@ -skeleton -======== - .. image:: https://img.shields.io/pypi/v/skeleton.svg :target: https://pypi.org/project/skeleton From 9d7352545487df482e168395b0570859a44b97c4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 25 Aug 2016 15:35:36 -0400 Subject: [PATCH 122/447] Fallback to __name__ if __package__ is None. Fixes #7. --- CHANGES.rst | 5 +++++ calendra/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1f92dae8..2fd007b9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,11 @@ CHANGELOG ========= +1.7.1 +----- + +- #7: Avoid crashing on import when installed as zip package. + 1.7 --- diff --git a/calendra/__init__.py b/calendra/__init__.py index 7fa79de4..acfed5d9 100644 --- a/calendra/__init__.py +++ b/calendra/__init__.py @@ -3,4 +3,4 @@ #: Module version, as defined in PEP-0396. -__version__ = pkg_resources.get_distribution(__package__).version +__version__ = pkg_resources.get_distribution(__package__ or __name__).version From 75d4885ca0de62f1f254ce422b06c9f8f1069008 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 25 Aug 2016 15:37:38 -0400 Subject: [PATCH 123/447] Added tag 1.7.1 for changeset dc6ab44ebf4e --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index b634a7e8..46c8eab2 100644 --- a/.hgtags +++ b/.hgtags @@ -12,3 +12,4 @@ f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 0a5dea7f7adbe859fd963b864ae0e969ac3e0fff 1.5 a1e714866c1e665caa834e812960293a55376d39 1.6 f9d369af37c9114940a9179d4707f60df62ae28b 1.7 +dc6ab44ebf4e7442e5340dfcfbe53e5ef6618dbb 1.7.1 From 629d80f45dedc801e3fe19215ba50114b4c7b949 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 4 Sep 2016 13:06:52 -0400 Subject: [PATCH 124/447] No need for a .gitignore file; projects may want to add one, but I recommend not having one unless the project has project-specific files to ignore. --- .gitignore | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index e69de29b..00000000 From 03c1cc86843bcfbb6c2a9366d285427ac006aeee Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 14 Sep 2016 21:00:50 -0400 Subject: [PATCH 125/447] Remove support for building docs, now that docs support for pypi is deprecated. I hope at some point RTD comes up with an API that once again allows automatic building of docs. --- setup.cfg | 2 +- setup.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/setup.cfg b/setup.cfg index dcd8d122..f5ee6072 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [aliases] -release = dists build_sphinx upload upload_docs +release = dists upload dists = clean --all sdist bdist_wheel test = pytest diff --git a/setup.py b/setup.py index 91e4110c..1f815f2e 100644 --- a/setup.py +++ b/setup.py @@ -12,9 +12,7 @@ needs_pytest = {'pytest', 'test'}.intersection(sys.argv) pytest_runner = ['pytest_runner'] if needs_pytest else [] -needs_sphinx = {'release', 'build_sphinx', 'upload_docs'}.intersection(sys.argv) -sphinx = ['sphinx', 'rst.linker'] if needs_sphinx else [] -needs_wheel = {'release', 'bdist_wheel'}.intersection(sys.argv) +needs_wheel = {'release', 'bdist_wheel', 'dists'}.intersection(sys.argv) wheel = ['wheel'] if needs_wheel else [] name = 'skeleton' @@ -37,7 +35,7 @@ }, setup_requires=[ 'setuptools_scm>=1.9', - ] + pytest_runner + sphinx + wheel, + ] + pytest_runner + wheel, tests_require=[ 'pytest>=2.8', ], From 750a2b38964adc868b1a7f4570afa1532418b12c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 22 Sep 2016 10:50:07 -0500 Subject: [PATCH 126/447] Use tox instead of pytest-runner --- .travis.yml | 3 +-- pytest.ini | 2 +- setup.cfg | 1 - setup.py | 7 +------ tests/requirements.txt | 1 + tox.ini | 5 +++++ 6 files changed, 9 insertions(+), 10 deletions(-) create mode 100644 tests/requirements.txt create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml index 9f4c5178..6c9a2cff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,7 @@ python: - 2.7 - 3.5 script: -- pip install -U pytest -- python setup.py test +- tox branches: except: - skeleton diff --git a/pytest.ini b/pytest.ini index 9752c365..56a87745 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] -norecursedirs=*.egg .eggs dist build +norecursedirs=dist build .tox addopts=--doctest-modules doctest_optionflags=ALLOW_UNICODE ELLIPSIS diff --git a/setup.cfg b/setup.cfg index f5ee6072..4659acce 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,6 @@ [aliases] release = dists upload dists = clean --all sdist bdist_wheel -test = pytest [wheel] universal = 1 diff --git a/setup.py b/setup.py index 1f815f2e..ec848c10 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,6 @@ with io.open('README.rst', encoding='utf-8') as readme: long_description = readme.read() -needs_pytest = {'pytest', 'test'}.intersection(sys.argv) -pytest_runner = ['pytest_runner'] if needs_pytest else [] needs_wheel = {'release', 'bdist_wheel', 'dists'}.intersection(sys.argv) wheel = ['wheel'] if needs_wheel else [] @@ -35,10 +33,7 @@ }, setup_requires=[ 'setuptools_scm>=1.9', - ] + pytest_runner + wheel, - tests_require=[ - 'pytest>=2.8', - ], + ] + wheel, classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 00000000..70bc02f1 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1 @@ +pytest >= 2.8 diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..dea83741 --- /dev/null +++ b/tox.ini @@ -0,0 +1,5 @@ +[testenv] +deps = + -r tests/requirements.txt + +commands = py.test From cc80be915b6912056990bf71324826e244432533 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Sep 2016 10:07:39 -0500 Subject: [PATCH 127/447] Use pkg_resources to resolve the version. Requires that the necessary package metadata have been built before building docs. --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9c7ad1b0..5abe25ae 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import setuptools_scm +import pkg_resources extensions = [ 'sphinx.ext.autodoc', @@ -13,7 +13,7 @@ copyright = '2016 Jason R. Coombs' # The short X.Y version. -version = setuptools_scm.get_version(root='..', relative_to=__file__) +version = pkg_resources.require(project)[0].version # The full version, including alpha/beta/rc tags. release = version From 8b4139a8132c330623631f84528a3cd8f186df9a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Sep 2016 10:08:41 -0500 Subject: [PATCH 128/447] Each requirement line is passed as a single parameter to pip, so you can't have a space separating the option and its value. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index dea83741..fa7284b8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [testenv] deps = - -r tests/requirements.txt + -rtests/requirements.txt commands = py.test From 4d382b3dee98d155f4057759ff015c3b6f0a15ed Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 28 Sep 2016 12:29:05 -0400 Subject: [PATCH 129/447] Python Packaging -- never do with one command what you can do with two. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6c9a2cff..d7871d87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ language: python python: - 2.7 - 3.5 +install: +- pip install tox script: - tox branches: From 12196ba3c3e116a2514ed9fd22c6ed60539e9160 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 30 Sep 2016 15:52:25 -0400 Subject: [PATCH 130/447] Provide a reference to the license declaration in the readme. Fixes jaraco/skeleton#1. --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 1c5d10ec..bb3d9127 100644 --- a/README.rst +++ b/README.rst @@ -7,3 +7,7 @@ .. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg :target: http://travis-ci.org/jaraco/skeleton + +License is indicated in the project metadata (typically one or more +of the Trove classifiers). For more details, see `this explanation +`_. From c4fd3f3cf414e2ee08ad53bd71cf9c201c69ca6f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 30 Sep 2016 16:14:13 -0400 Subject: [PATCH 131/447] Use usedevelop to workaround tox-dev/tox#373 --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index fa7284b8..564f2050 100644 --- a/tox.ini +++ b/tox.ini @@ -3,3 +3,4 @@ deps = -rtests/requirements.txt commands = py.test +usedevelop = True From 96984072229ae07471373da73cad377a0cb324ef Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 2 Oct 2016 09:28:19 -0500 Subject: [PATCH 132/447] Incorporate pre-release of setuptools to cause releases to include the PEP-420 deferral. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d7871d87..87881dbc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - 3.5 install: - pip install tox +- pip install https://github.com/pypa/setuptools/releases/download/v28.2.0b1/setuptools-28.2.0b1-py2.py3-none-any.whl script: - tox branches: From 1d7afbebd6530015d76ce93d88aa7a7c48c29717 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Oct 2016 12:16:38 -0700 Subject: [PATCH 133/447] Just upgrade to released setuptools now. --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 87881dbc..0c33b1ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,7 @@ python: - 2.7 - 3.5 install: -- pip install tox -- pip install https://github.com/pypa/setuptools/releases/download/v28.2.0b1/setuptools-28.2.0b1-py2.py3-none-any.whl +- pip install tox "setuptools>=28.2" script: - tox branches: From 9be6e615930bdecb69cf4da887eefd0d53c425bd Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 14 Oct 2016 21:40:51 -0400 Subject: [PATCH 134/447] Exclude versions of setuptools_scm due to pypa/setuptools_scm#109. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ec848c10..27ace5fd 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ extras_require={ }, setup_requires=[ - 'setuptools_scm>=1.9', + 'setuptools_scm>=1.9,!=1.13.1,!=1.14.0', ] + wheel, classifiers=[ "Development Status :: 5 - Production/Stable", From aa1f8ebe0d2d3f49a36535b61824f2fece3bdd46 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 14 Oct 2016 23:03:42 -0400 Subject: [PATCH 135/447] Allow passing posargs --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 564f2050..d740130c 100644 --- a/tox.ini +++ b/tox.ini @@ -2,5 +2,5 @@ deps = -rtests/requirements.txt -commands = py.test +commands = py.test {posargs} usedevelop = True From 60c7c186c133551cf0637354a642e49406f814b7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 15 Oct 2016 20:16:20 -0400 Subject: [PATCH 136/447] Need a later version of setuptools_scm until it's released. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0c33b1ce..0a6cb29f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,8 @@ script: branches: except: - skeleton +before_deploy: +- pip install https://dl.dropboxusercontent.com/u/54081/cheeseshop/setuptools_scm-1.14.1b1.tar.gz deploy: provider: pypi server: https://upload.pypi.org/legacy/ From 42ecbe7706cd756c5c3dff103fa3ff65e8a02349 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 20 Oct 2016 15:38:47 -0400 Subject: [PATCH 137/447] Update to setuptools_scm 1.15.0rc1 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0a6cb29f..6effc440 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ branches: except: - skeleton before_deploy: -- pip install https://dl.dropboxusercontent.com/u/54081/cheeseshop/setuptools_scm-1.14.1b1.tar.gz +- pip install https://github.com/pypa/setuptools_scm/archive/v1.15.0rc1.tar.gz deploy: provider: pypi server: https://upload.pypi.org/legacy/ From 95fd34c61f8d9df2e9c559b3978c85e7d03cd8d8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 20 Oct 2016 17:13:15 -0400 Subject: [PATCH 138/447] Gotta get an sdist - so use one jaraco built --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6effc440..cad38c8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ branches: except: - skeleton before_deploy: -- pip install https://github.com/pypa/setuptools_scm/archive/v1.15.0rc1.tar.gz +- pip install https://dl.dropboxusercontent.com/u/54081/cheeseshop/setuptools_scm-1.15.0rc1.tar.gz deploy: provider: pypi server: https://upload.pypi.org/legacy/ From 4eb8b429026daf7262c762eb62267cdf375c1aa8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 21 Oct 2016 09:15:07 -0400 Subject: [PATCH 139/447] Remove Mercurial metadata --- .hgtags | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .hgtags diff --git a/.hgtags b/.hgtags deleted file mode 100644 index 46c8eab2..00000000 --- a/.hgtags +++ /dev/null @@ -1,15 +0,0 @@ -e624db136314c527e764b41ec908c37603e9897a 0.2dev-20140827 -53be893b2edab43aa759608445e92b517195dc01 0.3-dev-20140827 -62b9caa9959662d71f3a5a83a6cd2f39833c3d54 1.0 -34730031b5d093faf63d9b215163ae56b123b38a 1.1 -f50a365320e44a79a1c154cb18b82531107baaba 1.1.1 -56af90f1e05681fe8472f5498c7587cfae222300 1.1.2 -9d252ce92c6f644442a19cbc19372ca709bd1018 1.1.3 -58d445d263c89344400a6f1c9e3631798262bfee 1.2 -5e89a1ee4d09a1b2000108ad4bb1f7d80ee87449 1.2.1 -774242ccc46081f5b6fd205bf650ff90c9e75e83 1.3 -58d4d242bca28593caf806adc14bea1c86c65434 1.4 -0a5dea7f7adbe859fd963b864ae0e969ac3e0fff 1.5 -a1e714866c1e665caa834e812960293a55376d39 1.6 -f9d369af37c9114940a9179d4707f60df62ae28b 1.7 -dc6ab44ebf4e7442e5340dfcfbe53e5ef6618dbb 1.7.1 From 63162fb67561e975cd20629d7f04917ae54fa4d6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 21 Oct 2016 09:34:52 -0400 Subject: [PATCH 140/447] Update badges to point to project --- README.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 9ebe19c8..7fe2790b 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,12 @@ -.. image:: https://img.shields.io/pypi/v/skeleton.svg - :target: https://pypi.org/project/skeleton +.. image:: https://img.shields.io/pypi/v/calendra.svg + :target: https://pypi.org/project/calendra -.. image:: https://img.shields.io/pypi/pyversions/skeleton.svg +.. image:: https://img.shields.io/pypi/pyversions/calendra.svg -.. image:: https://img.shields.io/pypi/dm/skeleton.svg +.. image:: https://img.shields.io/pypi/dm/calendra.svg -.. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg - :target: http://travis-ci.org/jaraco/skeleton +.. image:: https://img.shields.io/travis/jaraco/calendra/master.svg + :target: http://travis-ci.org/jaraco/calendra License is indicated in the project metadata (typically one or more of the Trove classifiers). For more details, see `this explanation From 200e6a525161b355d37862c9aee22c84e1413af4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 24 Oct 2016 10:07:33 -0400 Subject: [PATCH 141/447] Bump to setuptools_scm 1.15.0. --- .travis.yml | 2 -- setup.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index cad38c8f..0c33b1ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,6 @@ script: branches: except: - skeleton -before_deploy: -- pip install https://dl.dropboxusercontent.com/u/54081/cheeseshop/setuptools_scm-1.15.0rc1.tar.gz deploy: provider: pypi server: https://upload.pypi.org/legacy/ diff --git a/setup.py b/setup.py index 27ace5fd..83a22f68 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ extras_require={ }, setup_requires=[ - 'setuptools_scm>=1.9,!=1.13.1,!=1.14.0', + 'setuptools_scm>=1.15.0', ] + wheel, classifiers=[ "Development Status :: 5 - Production/Stable", From 4ee40ca2d13c2c8b544ad5f880193f5c0864648a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 4 Nov 2016 09:49:30 -0400 Subject: [PATCH 142/447] Update config to support building on ReadTheDocs --- docs/conf.py | 2 +- docs/requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 docs/requirements.txt diff --git a/docs/conf.py b/docs/conf.py index 5abe25ae..aa34defc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ master_doc = 'index' link_files = { - 'CHANGES.rst': dict( + '../CHANGES.rst': dict( using=dict( GH='https://github.com', project=project, diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..442df9fa --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +rst.linker From 18cb65f8f1c1eaf7b79a33fb5a2b7cbd1f851868 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 4 Nov 2016 10:16:12 -0400 Subject: [PATCH 143/447] Add note about the broken docs problem. --- README.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.rst b/README.rst index bb3d9127..5196a108 100644 --- a/README.rst +++ b/README.rst @@ -8,6 +8,20 @@ .. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg :target: http://travis-ci.org/jaraco/skeleton + +License +======= + License is indicated in the project metadata (typically one or more of the Trove classifiers). For more details, see `this explanation `_. + +Docs +==== + +There's `no good mechanism for publishing documentation +`_ +easily. If there's a documentation link above, it's probably +stale because PyPI-based documentation is deprecated. This +project may have documentation published at ReadTheDocs, but +probably not. Good luck finding it. From a50fb1c894f7985bb71edf8d0ce60ef4f350b745 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 15 Dec 2016 14:40:27 -0500 Subject: [PATCH 144/447] Skip upload docs as it's deprecated anyway --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 0c33b1ce..13ebb8aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,4 @@ deploy: user: jaraco # supply password with `travis encrypt --add deploy.password` distributions: dists + skip_upload_docs: true From 99ffa27f0e7bd2eae63c84a0ded567eba4a2394b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Dec 2016 08:25:01 -0500 Subject: [PATCH 145/447] Remove rant about docs. If there's no link to the docs, then this is the docs. --- README.rst | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.rst b/README.rst index 5196a108..0e0e26ee 100644 --- a/README.rst +++ b/README.rst @@ -15,13 +15,3 @@ License License is indicated in the project metadata (typically one or more of the Trove classifiers). For more details, see `this explanation `_. - -Docs -==== - -There's `no good mechanism for publishing documentation -`_ -easily. If there's a documentation link above, it's probably -stale because PyPI-based documentation is deprecated. This -project may have documentation published at ReadTheDocs, but -probably not. Good luck finding it. From 6245d0966d8dfb0fa2893c8a3e7d760c31d134d7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Dec 2016 08:28:03 -0500 Subject: [PATCH 146/447] Prefer get_distribution --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index aa34defc..46d614b9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,7 @@ copyright = '2016 Jason R. Coombs' # The short X.Y version. -version = pkg_resources.require(project)[0].version +version = pkg_resources.get_distribution(project).version # The full version, including alpha/beta/rc tags. release = version From 3da8cf4a6f14abf5da05c9d46f3362dcc43d71a4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Dec 2016 08:42:35 -0500 Subject: [PATCH 147/447] No longer rely on the package being installed to retrieve the version. Instead, load the project name and version by invoking the setup script. --- docs/conf.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 46d614b9..adc9df77 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import pkg_resources +import os +import sys +import subprocess extensions = [ 'sphinx.ext.autodoc', @@ -9,11 +11,15 @@ ] # General information about the project. -project = 'skeleton' + +root = os.path.join(os.path.dirname(__file__), '..') +setup_script = os.path.join(root, 'setup.py') +dist_info_cmd = [sys.executable, setup_script, '--name', '--version'] +output_bytes = subprocess.check_output(dist_info_cmd, cwd=root) +project, version = output_bytes.decode('utf-8').split() + copyright = '2016 Jason R. Coombs' -# The short X.Y version. -version = pkg_resources.get_distribution(project).version # The full version, including alpha/beta/rc tags. release = version From fbadf0344d4b9ac6917e8546b5529c20082f4733 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Dec 2016 08:44:55 -0500 Subject: [PATCH 148/447] Also get the URL from the project metadata --- docs/conf.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index adc9df77..d52e40d3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,9 +14,9 @@ root = os.path.join(os.path.dirname(__file__), '..') setup_script = os.path.join(root, 'setup.py') -dist_info_cmd = [sys.executable, setup_script, '--name', '--version'] +dist_info_cmd = [sys.executable, setup_script, '--name', '--version', '--url'] output_bytes = subprocess.check_output(dist_info_cmd, cwd=root) -project, version = output_bytes.decode('utf-8').split() +project, version, url = output_bytes.decode('utf-8').split() copyright = '2016 Jason R. Coombs' @@ -30,11 +30,12 @@ using=dict( GH='https://github.com', project=project, + url=url, ), replace=[ dict( pattern=r"(Issue )?#(?P\d+)", - url='{GH}/jaraco/{project}/issues/{issue}', + url='{url}/issues/{issue}', ), dict( pattern=r"^(?m)((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n", From b2c592d84bc5f7c16a70b6d593fb320c5559eeee Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Dec 2016 08:47:03 -0500 Subject: [PATCH 149/447] Also grab the author from the package metadata --- docs/conf.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d52e40d3..23a24476 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,11 +14,12 @@ root = os.path.join(os.path.dirname(__file__), '..') setup_script = os.path.join(root, 'setup.py') -dist_info_cmd = [sys.executable, setup_script, '--name', '--version', '--url'] +fields = ['--name', '--version', '--url', '--author'] +dist_info_cmd = [sys.executable, setup_script] + fields output_bytes = subprocess.check_output(dist_info_cmd, cwd=root) -project, version, url = output_bytes.decode('utf-8').split() +project, version, url, author = output_bytes.decode('utf-8').split() -copyright = '2016 Jason R. Coombs' +copyright = '2016 ' + author # The full version, including alpha/beta/rc tags. release = version From b1133de832c3960777b9db80c070885c4bedd7c4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Dec 2016 08:57:20 -0500 Subject: [PATCH 150/447] Strip the trailing newline and then split on newline. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 23a24476..7402f72f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,7 +17,7 @@ fields = ['--name', '--version', '--url', '--author'] dist_info_cmd = [sys.executable, setup_script] + fields output_bytes = subprocess.check_output(dist_info_cmd, cwd=root) -project, version, url, author = output_bytes.decode('utf-8').split() +project, version, url, author = output_bytes.decode('utf-8').strip().split('\n') copyright = '2016 ' + author From 84b53d90527040c58c4236698c95cb6cd1f2736d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 2 Jan 2017 15:54:14 -0500 Subject: [PATCH 151/447] Default upload URL is now in Python 3.6. Use that. --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 4659acce..e0803242 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,6 +4,3 @@ dists = clean --all sdist bdist_wheel [wheel] universal = 1 - -[upload] -repository = https://upload.pypi.org/legacy/ From 5853c7e7e738bc641f95835f239b04d8c7a853e3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 5 Jan 2017 09:31:05 -0500 Subject: [PATCH 152/447] setup is already present in the module name. Just call them params. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 83a22f68..45243679 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ name = 'skeleton' description = '' -setup_params = dict( +params = dict( name=name, use_scm_version=True, author="Jason R. Coombs", @@ -45,4 +45,4 @@ }, ) if __name__ == '__main__': - setuptools.setup(**setup_params) + setuptools.setup(**params) From 746dd7999f9db23276144ee2160920bd01ed860c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 17 Jan 2017 19:56:28 -0500 Subject: [PATCH 153/447] Use Python 3.6 by default --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 13ebb8aa..91ba39a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: false language: python python: - 2.7 -- 3.5 +- 3.6 install: - pip install tox "setuptools>=28.2" script: @@ -16,7 +16,7 @@ deploy: on: tags: true all_branches: true - python: 3.5 + python: 3.6 user: jaraco # supply password with `travis encrypt --add deploy.password` distributions: dists From 9f6eea591eaae483be11d13ebad06958a6a1e2c8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 19 Jan 2017 11:13:08 -0500 Subject: [PATCH 154/447] No longer rely on setup_requires for wheel. --- setup.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 45243679..157ecb2e 100644 --- a/setup.py +++ b/setup.py @@ -3,16 +3,12 @@ # Project skeleton maintained at https://github.com/jaraco/skeleton import io -import sys import setuptools with io.open('README.rst', encoding='utf-8') as readme: long_description = readme.read() -needs_wheel = {'release', 'bdist_wheel', 'dists'}.intersection(sys.argv) -wheel = ['wheel'] if needs_wheel else [] - name = 'skeleton' description = '' @@ -33,7 +29,7 @@ }, setup_requires=[ 'setuptools_scm>=1.15.0', - ] + wheel, + ], classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", From 21a4e86e6fef7fba92afb4bcb49d79859d0cb2b2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 23 Jan 2017 07:10:36 -0500 Subject: [PATCH 155/447] Add PEP substitution in changelog. --- docs/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 7402f72f..8639b2cc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,6 +42,10 @@ pattern=r"^(?m)((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n", with_scm="{text}\n{rev[timestamp]:%d %b %Y}\n", ), + dict( + pattern=r"PEP[- ](?P\d+)", + url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/', + ), ], ), } From ee0d8647d8537b9de2aeafeba4acd74910f98a4f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 24 Jan 2017 20:59:15 -0500 Subject: [PATCH 156/447] Add support for Python 2.6 in docs conf --- docs/conf.py | 3 +++ tests/requirements.txt | 1 + 2 files changed, 4 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 8639b2cc..7402c7a1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,6 +5,9 @@ import sys import subprocess +if 'check_output' not in dir(subprocess): + import subprocess32 as subprocess + extensions = [ 'sphinx.ext.autodoc', 'rst.linker', diff --git a/tests/requirements.txt b/tests/requirements.txt index 70bc02f1..ab484054 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1 +1,2 @@ pytest >= 2.8 +subprocess32; python_version=="2.6" From e690b031cc3c07b657dc235252a284d8023a38dc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 9 Feb 2017 12:29:49 -0500 Subject: [PATCH 157/447] Set the origin date once and forget it. --- docs/conf.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 7402c7a1..bf6ae64f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,6 +4,7 @@ import os import sys import subprocess +import datetime if 'check_output' not in dir(subprocess): import subprocess32 as subprocess @@ -22,7 +23,10 @@ output_bytes = subprocess.check_output(dist_info_cmd, cwd=root) project, version, url, author = output_bytes.decode('utf-8').strip().split('\n') -copyright = '2016 ' + author +origin_date = datetime.date(2017,1,1) +today = datetime.date.today() + +copyright = '{origin_date.year}-{today.year} {author}'.format(**locals()) # The full version, including alpha/beta/rc tags. release = version From b728c5892b394392044b245cba43f46740efb851 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 12 Mar 2017 13:33:19 -0400 Subject: [PATCH 158/447] Add python_requires directive. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 157ecb2e..892b6b35 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ packages=setuptools.find_packages(), include_package_data=True, namespace_packages=name.split('.')[:-1], + python_requires='>=2.7', install_requires=[ ], extras_require={ From 59c37d70f1140bf18b9a48398cc4502ebce91b5e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 29 Mar 2017 14:36:54 -0400 Subject: [PATCH 159/447] Don't bother with copyright year(s). Let the repository history track the changes and copyright years. YAGNI. --- docs/conf.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index bf6ae64f..fc947971 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,10 +23,7 @@ output_bytes = subprocess.check_output(dist_info_cmd, cwd=root) project, version, url, author = output_bytes.decode('utf-8').strip().split('\n') -origin_date = datetime.date(2017,1,1) -today = datetime.date.today() - -copyright = '{origin_date.year}-{today.year} {author}'.format(**locals()) +copyright = author # The full version, including alpha/beta/rc tags. release = version From 049284c6c440217c4f686b61c0980b5e0100626b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 29 Mar 2017 14:39:02 -0400 Subject: [PATCH 160/447] Include the project (for docstrings). Include Sphinx (for environments where it's not an implied provision). --- docs/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index 442df9fa..c11e7555 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,3 @@ +. +sphinx rst.linker From b9bcd869482ea0ff636c8848896a94e24a6fbfca Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 29 Mar 2017 14:40:02 -0400 Subject: [PATCH 161/447] Include pytest-sugar for nicer test output. --- tests/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/requirements.txt b/tests/requirements.txt index ab484054..6d65b375 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,2 +1,3 @@ pytest >= 2.8 +pytest-sugar subprocess32; python_version=="2.6" From 908cf4ad0e27813933ada7cc9f16ebce9ac0c6cc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 30 Mar 2017 04:36:53 -0400 Subject: [PATCH 162/447] Rely on jaraco.packaging for loading the package metadata from the package for Sphinx. --- docs/conf.py | 27 ++------------------------- docs/requirements.txt | 3 ++- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index fc947971..0e11c827 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,46 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import os -import sys -import subprocess -import datetime - -if 'check_output' not in dir(subprocess): - import subprocess32 as subprocess - extensions = [ 'sphinx.ext.autodoc', + 'jaraco.packaging.sphinx', 'rst.linker', ] -# General information about the project. - -root = os.path.join(os.path.dirname(__file__), '..') -setup_script = os.path.join(root, 'setup.py') -fields = ['--name', '--version', '--url', '--author'] -dist_info_cmd = [sys.executable, setup_script] + fields -output_bytes = subprocess.check_output(dist_info_cmd, cwd=root) -project, version, url, author = output_bytes.decode('utf-8').strip().split('\n') - -copyright = author - -# The full version, including alpha/beta/rc tags. -release = version - master_doc = 'index' link_files = { '../CHANGES.rst': dict( using=dict( GH='https://github.com', - project=project, - url=url, ), replace=[ dict( pattern=r"(Issue )?#(?P\d+)", - url='{url}/issues/{issue}', + url='{package_url}/issues/{issue}', ), dict( pattern=r"^(?m)((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n", diff --git a/docs/requirements.txt b/docs/requirements.txt index c11e7555..e7b6a745 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,4 @@ . sphinx -rst.linker +jaraco.packaging>=3.2 +rst.linker>=1.9 From 689f700fcfcdfcdc7d027f204a9654b101ac9ecb Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 30 Mar 2017 04:43:46 -0400 Subject: [PATCH 163/447] Use single-quotes to satisfy the style nazis. --- docs/conf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 0e11c827..8bc82981 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,15 +16,15 @@ ), replace=[ dict( - pattern=r"(Issue )?#(?P\d+)", + pattern=r'(Issue )?#(?P\d+)', url='{package_url}/issues/{issue}', ), dict( - pattern=r"^(?m)((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n", - with_scm="{text}\n{rev[timestamp]:%d %b %Y}\n", + pattern=r'^(?m)((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n', + with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', ), dict( - pattern=r"PEP[- ](?P\d+)", + pattern=r'PEP[- ](?P\d+)', url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/', ), ], From 23dae906a2563a2da30e0d67490fa009576f6439 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 30 Mar 2017 20:41:18 -0400 Subject: [PATCH 164/447] The requirement is no longer needed for tests. --- tests/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index 6d65b375..d9e0f332 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,2 @@ pytest >= 2.8 pytest-sugar -subprocess32; python_version=="2.6" From fbe7cb7fa1c0f4e30f6ac6e886c49ab6491aa959 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 21 Apr 2017 12:21:45 -0400 Subject: [PATCH 165/447] Add readthedocs yml file --- .readthedocs.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .readthedocs.yml diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..e83d731b --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,3 @@ +requirements_file: docs/requirements.txt +python: + version: 3 From 243e44fdb797ae54a08eb02d924f88e775e74ba9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 21 Apr 2017 12:31:54 -0400 Subject: [PATCH 166/447] Move requirements for docs and testing into extras --- .readthedocs.yml | 4 +++- docs/requirements.txt | 4 ---- setup.py | 9 +++++++++ tests/requirements.txt | 2 -- tox.ini | 4 +--- 5 files changed, 13 insertions(+), 10 deletions(-) delete mode 100644 docs/requirements.txt delete mode 100644 tests/requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml index e83d731b..8ae44684 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,3 +1,5 @@ -requirements_file: docs/requirements.txt python: version: 3 + extra_requirements: + - docs + pip_install: true diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index e7b6a745..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -. -sphinx -jaraco.packaging>=3.2 -rst.linker>=1.9 diff --git a/setup.py b/setup.py index 892b6b35..2bed3f7c 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,15 @@ install_requires=[ ], extras_require={ + 'testing': [ + 'pytest>=2.8', + 'pytest-sugar', + ], + 'docs': [ + 'sphinx', + 'jaraco.packaging>=3.2', + 'rst.linker>=1.9', + ], }, setup_requires=[ 'setuptools_scm>=1.15.0', diff --git a/tests/requirements.txt b/tests/requirements.txt deleted file mode 100644 index d9e0f332..00000000 --- a/tests/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest >= 2.8 -pytest-sugar diff --git a/tox.ini b/tox.ini index d740130c..8efcba6f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,4 @@ [testenv] -deps = - -rtests/requirements.txt - commands = py.test {posargs} usedevelop = True +extras = testing From 3383a3aceb435cef929c13dff3e54e46af01cf49 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 26 Apr 2017 10:29:35 -0400 Subject: [PATCH 167/447] Add appveyor script for CI testing on Windows. --- appveyor.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 appveyor.yml diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..e856af3b --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,19 @@ +environment: + + APPVEYOR: true + + matrix: + - PYTHON: "C:\\Python36-x64" + - PYTHON: "C:\\Python27-x64" + +install: + # symlink python from a directory with a space + - "mklink /d \"C:\\Program Files\\Python\" %PYTHON%" + - "SET PYTHON=\"C:\\Program Files\\Python\"" + - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" + +build: off + +test_script: + - "python -m pip install tox" + - "tox" From 110cb56c59e99c5d0c630612f8593a7ef55ce732 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 28 Apr 2017 19:21:34 -0400 Subject: [PATCH 168/447] Require tox 2.4 or later; fixes #2. --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 8efcba6f..1ae06efe 100644 --- a/tox.ini +++ b/tox.ini @@ -1,3 +1,6 @@ +[tox] +minversion = 2.4 + [testenv] commands = py.test {posargs} usedevelop = True From c84284022a198d560e685c5a687458a5be4c5fe6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 12 May 2017 21:42:53 -0400 Subject: [PATCH 169/447] Remove namespace_packages declaration, no longer needed. --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 2bed3f7c..a7aeae12 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,6 @@ url="https://github.com/jaraco/" + name, packages=setuptools.find_packages(), include_package_data=True, - namespace_packages=name.split('.')[:-1], python_requires='>=2.7', install_requires=[ ], From 31fb9d19ee7751ced7a0339268b2cd7b3ceb4701 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 4 Jun 2017 14:02:51 -0400 Subject: [PATCH 170/447] Use a simple build number rather than prefixing with '1.0.' --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index e856af3b..0a8ce5c5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -17,3 +17,5 @@ build: off test_script: - "python -m pip install tox" - "tox" + +version: '{build}' From 16d68a9fd19e6a726cc86fd1e3ec5f2d24788345 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 14 Aug 2017 08:14:57 -0400 Subject: [PATCH 171/447] Restore support for namespace package declaration, selected on a 'nspkg_technique' setting --- setup.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/setup.py b/setup.py index a7aeae12..72d901c4 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,11 @@ name = 'skeleton' description = '' +nspkg_technique = 'native' +""" +Does this package use "native" namespace packages or +pkg_resources "managed" namespace packages? +""" params = dict( name=name, @@ -22,6 +27,10 @@ url="https://github.com/jaraco/" + name, packages=setuptools.find_packages(), include_package_data=True, + namespace_packages=( + name.split('.')[:-1] if nspkg_technique == 'managed' + else [] + ), python_requires='>=2.7', install_requires=[ ], From 250cb960021233160e78a6f2c2780cfc1c964b9c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 26 Aug 2017 15:35:09 -0400 Subject: [PATCH 172/447] Inspired by pypa/setuptools#1059, use the preferred bdist_wheel heading. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e0803242..b0c90cbf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,5 +2,5 @@ release = dists upload dists = clean --all sdist bdist_wheel -[wheel] +[bdist_wheel] universal = 1 From 88d315ae9adab430bd36722da8c6ab74c2e79cf0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 13 Sep 2017 04:24:11 -0400 Subject: [PATCH 173/447] Check the docs during tests --- setup.py | 1 + tox.ini | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 72d901c4..75f23712 100644 --- a/setup.py +++ b/setup.py @@ -38,6 +38,7 @@ 'testing': [ 'pytest>=2.8', 'pytest-sugar', + 'collective.checkdocs', ], 'docs': [ 'sphinx', diff --git a/tox.ini b/tox.ini index 1ae06efe..16bf78a7 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,8 @@ minversion = 2.4 [testenv] -commands = py.test {posargs} +commands = + py.test {posargs} + python setup.py checkdocs usedevelop = True extras = testing From 835393c93e4fec867b5e2e0a638e7a14994c6b1d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 18 Sep 2017 18:05:16 -0400 Subject: [PATCH 174/447] Use stages in travis to have deployment depend on success in all Python versions. --- .travis.yml | 49 +++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 91ba39a9..e2914e2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,32 @@ +dist: trusty sudo: false language: python -python: -- 2.7 -- 3.6 + +jobs: + fast_finish: true + include: + - python: 2.7 + - python: &latest_py3 3.6 + - stage: deploy + if: tag IS present + python: *latest_py3 + install: skip + script: skip + before_deploy: python bootstrap.py + deploy: + provider: pypi + on: + tags: true + all_branches: true + user: jaraco + # supply password with `travis encrypt --add deploy.password` + distributions: dists + skip_cleanup: true + skip_upload_docs: true + +cache: pip + install: -- pip install tox "setuptools>=28.2" -script: -- tox -branches: - except: - - skeleton -deploy: - provider: pypi - server: https://upload.pypi.org/legacy/ - on: - tags: true - all_branches: true - python: 3.6 - user: jaraco - # supply password with `travis encrypt --add deploy.password` - distributions: dists - skip_upload_docs: true +- pip install tox + +script: tox From a419524c69e7c2f8b9327ff2f6ec9f61e89c9c30 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 19 Sep 2017 19:46:21 -0400 Subject: [PATCH 175/447] Remove 'bootstrap', artifact from setuptools --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e2914e2b..7932518e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,6 @@ jobs: python: *latest_py3 install: skip script: skip - before_deploy: python bootstrap.py deploy: provider: pypi on: From a953d1baa6c6cd2b4b9e5f06378a706b3555259d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 21 Sep 2017 16:36:17 -0400 Subject: [PATCH 176/447] --add doesn't work in a list --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7932518e..d4eecec2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,8 @@ jobs: tags: true all_branches: true user: jaraco - # supply password with `travis encrypt --add deploy.password` + password: + secure: ... # encrypt password with `travis encrypt` distributions: dists skip_cleanup: true skip_upload_docs: true From 5dc924c102a23d06cafd8e0850f0f35582cbd9aa Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 26 Sep 2017 12:02:30 +0100 Subject: [PATCH 177/447] Add a license file. Fixes jaraco/skeleton#1. --- LICENSE | 7 +++++++ README.rst | 8 -------- 2 files changed, 7 insertions(+), 8 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..5e795a61 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright Jason R. Coombs + +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. diff --git a/README.rst b/README.rst index 0e0e26ee..1c5d10ec 100644 --- a/README.rst +++ b/README.rst @@ -7,11 +7,3 @@ .. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg :target: http://travis-ci.org/jaraco/skeleton - - -License -======= - -License is indicated in the project metadata (typically one or more -of the Trove classifiers). For more details, see `this explanation -`_. From d149ed4d5dc659c81d0567b227523d95ee8e04c3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 16 Oct 2017 12:13:14 -0400 Subject: [PATCH 178/447] Remove downloads shield, no longer available. --- README.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.rst b/README.rst index 1c5d10ec..5161ae54 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,5 @@ .. image:: https://img.shields.io/pypi/pyversions/skeleton.svg -.. image:: https://img.shields.io/pypi/dm/skeleton.svg - .. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg :target: http://travis-ci.org/jaraco/skeleton From 34958ccc94b8c473ebf8adcdc35b82b6023d8702 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 17 Oct 2017 19:47:02 -0400 Subject: [PATCH 179/447] Add documentation badge. --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 5161ae54..bcdb31e5 100644 --- a/README.rst +++ b/README.rst @@ -5,3 +5,6 @@ .. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg :target: http://travis-ci.org/jaraco/skeleton + +.. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest + :target: http://skeleton.readthedocs.io/en/latest/?badge=latest From 6c36336e9fc45048ad43e4ff494c9d1ffc14fc49 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 13 Nov 2017 09:14:44 -0500 Subject: [PATCH 180/447] Normalize indentation in docs/conf.py --- docs/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8bc82981..14744ee8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,9 +2,9 @@ # -*- coding: utf-8 -*- extensions = [ - 'sphinx.ext.autodoc', - 'jaraco.packaging.sphinx', - 'rst.linker', + 'sphinx.ext.autodoc', + 'jaraco.packaging.sphinx', + 'rst.linker', ] master_doc = 'index' From 99622ab0e3d295a3ec17f69fb21dc68c94cc7fda Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 15 Nov 2017 10:26:38 -0500 Subject: [PATCH 181/447] Declare 'python' factor at top level --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d4eecec2..c7ed90b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,12 @@ dist: trusty sudo: false language: python +python: +- 2.7 +- &latest_py3 3.6 + jobs: fast_finish: true - include: - - python: 2.7 - - python: &latest_py3 3.6 - stage: deploy if: tag IS present python: *latest_py3 From ce5e0eb6ef5943bfa15edf5f9ea3f74e71ab00e4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 16 Nov 2017 13:17:16 -0500 Subject: [PATCH 182/447] Correct travis syntax --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c7ed90b2..61180dba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ python: jobs: fast_finish: true + include: - stage: deploy if: tag IS present python: *latest_py3 From b4b4c1116886f7cb10729a2d42272e41618ca20f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 16 Nov 2017 15:45:57 -0500 Subject: [PATCH 183/447] reference the license file in metadata --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index b0c90cbf..378a8e4f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,3 +4,6 @@ dists = clean --all sdist bdist_wheel [bdist_wheel] universal = 1 + +[metadata] +license_file = LICENSE From 28de240dff8f71333f742db7f9fb28b884eb40d7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 14:24:26 -0500 Subject: [PATCH 184/447] Use observation_shift to honor shift_new_years_day. --- calendra/core.py | 17 +++++------- calendra/tests/test_canada.py | 50 +++++++++++++++++++++++------------ calendra/tests/test_europe.py | 3 ++- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/calendra/core.py b/calendra/core.py index d9c05ccb..69bda53d 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -523,19 +523,14 @@ class WesternCalendar(Calendar): WEEKEND_DAYS = (SAT, SUN) shift_new_years_day = False - FIXED_HOLIDAYS = ( - Holiday( - date(2000, 1, 1), 'New year', indication='First day in January'), - ) - def get_variable_days(self, year): days = super(WesternCalendar, self).get_variable_days(year) - new_year = date(year, 1, 1) - if self.shift_new_years_day: - if new_year.weekday() in self.get_weekend_days(): - days.append(( - self.find_following_working_day(new_year), - "New Year shift")) + new_years = Holiday( + date(year, 1, 1), 'New year', indication='First day in January', + ) + if not self.shift_new_years_day: + new_years.observance_shift = None + days.append(new_years) return days diff --git a/calendra/tests/test_canada.py b/calendra/tests/test_canada.py index 53e25fef..0c9f27d6 100644 --- a/calendra/tests/test_canada.py +++ b/calendra/tests/test_canada.py @@ -12,15 +12,17 @@ class CanadaTest(GenericCalendarTest): def test_holidays_2011(self): holidays = self.cal.holidays_set(2011) - self.assertIn(date(2011, 1, 3), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2011, 1, 3), observed) self.assertIn(date(2011, 7, 1), holidays) self.assertIn(date(2011, 9, 5), holidays) self.assertIn(date(2011, 12, 26), holidays) def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) # New years shift - self.assertIn(date(2012, 7, 2), holidays) # Canada day shift + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) # New years shift + self.assertIn(date(2012, 7, 2), observed) # Canada day shift self.assertIn(date(2012, 9, 3), holidays) # Labour day self.assertIn(date(2012, 12, 25), holidays) @@ -35,7 +37,8 @@ def test_holidays_2013(self): def test_holidays_2017(self): holidays = self.cal.holidays_set(2017) - self.assertIn(date(2017, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2017, 1, 2), observed) class OntarioTest(GenericCalendarTest): @@ -48,7 +51,8 @@ def test_holidays_2010(self): def test_holidays_2011(self): holidays = self.cal.holidays_set(2011) - self.assertIn(date(2011, 1, 3), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2011, 1, 3), observed) self.assertIn(date(2011, 2, 21), holidays) # Family Day Ontario self.assertIn(date(2011, 4, 22), holidays) # Good Friday self.assertNotIn(date(2011, 4, 25), holidays) # Easter Monday @@ -62,7 +66,8 @@ def test_holidays_2011(self): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertIn(date(2012, 2, 20), holidays) # Family Day Ontario self.assertIn(date(2012, 4, 6), holidays) # Good Friday self.assertNotIn(date(2012, 4, 9), holidays) # Easter Monday @@ -80,7 +85,8 @@ class QuebecTest(GenericCalendarTest): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertNotIn(date(2012, 4, 6), holidays) # Good Friday self.assertIn(date(2012, 4, 9), holidays) # Easter Monday self.assertIn(date(2012, 5, 21), holidays) # Victoria Day @@ -96,7 +102,8 @@ class BritishColumbiaTest(GenericCalendarTest): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertIn(date(2012, 2, 13), holidays) # Family Day BC self.assertIn(date(2012, 4, 6), holidays) # Good Friday self.assertNotIn(date(2012, 4, 9), holidays) # Easter Monday @@ -114,7 +121,8 @@ class AlbertaTest(GenericCalendarTest): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertIn(date(2012, 2, 20), holidays) # Family Day self.assertIn(date(2012, 4, 6), holidays) # Good Friday self.assertNotIn(date(2012, 4, 9), holidays) # Easter Monday @@ -132,7 +140,8 @@ class SaskatchewanTest(GenericCalendarTest): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertIn(date(2012, 2, 20), holidays) # Family Day self.assertIn(date(2012, 4, 6), holidays) # Good Friday self.assertNotIn(date(2012, 4, 9), holidays) # Easter Monday @@ -151,7 +160,8 @@ class ManitobaTest(GenericCalendarTest): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertIn(date(2012, 2, 20), holidays) # Louis Riel Day self.assertIn(date(2012, 4, 6), holidays) # Good Friday self.assertNotIn(date(2012, 4, 9), holidays) # Easter Monday @@ -171,7 +181,8 @@ class NewBrunswickTest(GenericCalendarTest): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertNotIn(date(2012, 2, 20), holidays) # Family Day self.assertIn(date(2012, 4, 6), holidays) # Good Friday self.assertNotIn(date(2012, 4, 9), holidays) # Easter Monday @@ -191,7 +202,8 @@ class NovaScotiaTest(GenericCalendarTest): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertNotIn(date(2012, 2, 20), holidays) # Family Day self.assertIn(date(2012, 4, 6), holidays) # Good Friday self.assertNotIn(date(2012, 4, 9), holidays) # Easter Monday @@ -215,7 +227,8 @@ class PrinceEdwardIslandTest(GenericCalendarTest): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertIn(date(2012, 2, 20), holidays) # Islander Day self.assertIn(date(2012, 4, 6), holidays) # Good Friday self.assertNotIn(date(2012, 4, 9), holidays) # Easter Monday @@ -248,7 +261,8 @@ class YukonTest(GenericCalendarTest): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertNotIn(date(2012, 2, 20), holidays) # Family Day self.assertIn(date(2012, 4, 6), holidays) # Good Friday self.assertNotIn(date(2012, 4, 9), holidays) # Easter Monday @@ -269,7 +283,8 @@ class NorthwestTerritoriesTest(GenericCalendarTest): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertNotIn(date(2012, 2, 20), holidays) # Family Day self.assertIn(date(2012, 4, 6), holidays) # Good Friday self.assertNotIn(date(2012, 4, 9), holidays) # Easter Monday @@ -290,7 +305,8 @@ class NunavutTests(GenericCalendarTest): def test_holidays_2012(self): holidays = self.cal.holidays_set(2012) - self.assertIn(date(2012, 1, 2), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) self.assertNotIn(date(2012, 2, 20), holidays) # Family Day self.assertIn(date(2012, 4, 6), holidays) # Good Friday self.assertNotIn(date(2012, 4, 9), holidays) # Easter Monday diff --git a/calendra/tests/test_europe.py b/calendra/tests/test_europe.py index de061661..2a11e563 100644 --- a/calendra/tests/test_europe.py +++ b/calendra/tests/test_europe.py @@ -524,7 +524,8 @@ def test_year_2013(self): def test_shift_2012(self): holidays = self.cal.holidays_set(2012) self.assertIn(date(2012, 1, 1), holidays) # new year day - self.assertIn(date(2012, 1, 2), holidays) # new year day shift + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2012, 1, 2), observed) # new year day shift def test_shift_2011(self): holidays = self.cal.holidays_set(2011) From ef03360c062c5edf645342bf16220bfbe5f9f849 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 14:37:23 -0500 Subject: [PATCH 185/447] While bypassing Brazil.get_variable_days, ensure other parent classes' method is called. --- calendra/america/brazil.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/calendra/america/brazil.py b/calendra/america/brazil.py index aec34837..8d8e7283 100644 --- a/calendra/america/brazil.py +++ b/calendra/america/brazil.py @@ -399,7 +399,10 @@ def get_variable_days(self, year): ) ] - return non_fixed_holidays + non_working_days + return ( + super(Brazil, self).get_variable_days(year) + + non_fixed_holidays + non_working_days + ) def find_following_working_day(self, day): """ From e59905041a3d37ec8e52cea8f91723c1c36a2df5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 14:42:48 -0500 Subject: [PATCH 186/447] Update test to reflect indicated and observed holidays. --- calendra/tests/test_europe.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/calendra/tests/test_europe.py b/calendra/tests/test_europe.py index 047ccc2c..747a2d66 100644 --- a/calendra/tests/test_europe.py +++ b/calendra/tests/test_europe.py @@ -735,9 +735,10 @@ def test_shift_2015(self): def test_shift_2016(self): holidays = self.cal.holidays_set(2016) + observed = set(map(self.cal.get_observed_date, holidays)) self.assertIn(date(2016, 12, 25), holidays) # Christmas - Sunday - self.assertIn(date(2016, 12, 26), holidays) # Boxing day - Monday - self.assertIn(date(2016, 12, 27), holidays) # Christmas - shift to Tue + self.assertIn(date(2016, 12, 26), observed) # Christmas - observed + self.assertIn(date(2016, 12, 27), observed) # Boxing day - observed class UnitedKingdomNorthernIrelandTest(UnitedKingdomTest): From 76a72c2c120f92059e37a39787a74540d2b07c60 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 16:20:57 -0500 Subject: [PATCH 187/447] Restore use of Holiday class in UnitedStates. --- calendra/tests/test_usa.py | 11 ++- calendra/usa/core.py | 166 +++++++++++++------------------------ 2 files changed, 64 insertions(+), 113 deletions(-) diff --git a/calendra/tests/test_usa.py b/calendra/tests/test_usa.py index d6c6ad11..6d29dbd5 100644 --- a/calendra/tests/test_usa.py +++ b/calendra/tests/test_usa.py @@ -375,23 +375,26 @@ def test_state_year_2014(self): holidays = self.cal.holidays_set(2014) self.assertIn(date(2014, 3, 31), holidays) # Seward's Day self.assertIn(date(2014, 10, 18), holidays) # Alaska Day + observed = set(map(self.cal.get_observed_date, holidays)) # Alaska Day is on SAT, shift to FRI - self.assertIn(date(2014, 10, 17), holidays) + self.assertIn(date(2014, 10, 17), observed) def test_state_year_2015(self): holidays = self.cal.holidays_set(2015) self.assertIn(date(2015, 3, 30), holidays) # Seward's Day self.assertIn(date(2015, 10, 18), holidays) # Alaska Day + observed = set(map(self.cal.get_observed_date, holidays)) # Alaska day is on SUN: shifted to MON - self.assertIn(date(2015, 10, 19), holidays) + self.assertIn(date(2015, 10, 19), observed) def test_state_year_2017(self): holidays = self.cal.holidays_set(2017) self.assertIn(date(2017, 3, 27), holidays) # Seward's Day self.assertIn(date(2017, 10, 18), holidays) # Alaska Day + observed = set(map(self.cal.get_observed_date, holidays)) # Alaska day is on WED: no shift - self.assertNotIn(date(2017, 10, 19), holidays) - self.assertNotIn(date(2017, 10, 17), holidays) + self.assertNotIn(date(2017, 10, 19), observed) + self.assertNotIn(date(2017, 10, 17), observed) class ArizonaTest(UnitedStatesTest): diff --git a/calendra/usa/core.py b/calendra/usa/core.py index 2f9ea350..5c0f7a51 100644 --- a/calendra/usa/core.py +++ b/calendra/usa/core.py @@ -4,15 +4,24 @@ from datetime import date, timedelta +from dateutil import relativedelta as rd + from ..core import WesternCalendar, ChristianMixin from ..core import SUN, MON, TUE, WED, THU, FRI, SAT +from ..core import Holiday class UnitedStates(WesternCalendar, ChristianMixin): "United States of America" FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( - (7, 4, 'Independence Day'), + Holiday( + date(2000, 7, 4), + 'Independence Day', + indication='July 4', + observance_shift=Holiday.nearest_weekday, + ), ) + # Veterans day label veterans_day_label = 'Veterans Day' @@ -59,56 +68,6 @@ class UnitedStates(WesternCalendar, ChristianMixin): # Some regional variants include_mardi_gras = False - # Shift day mechanism - # These days won't be shifted to next MON or previous FRI - shift_exceptions = ( - # Exemple: - # (11, 11), # Veterans day won't be shifted - ) - - def shift(self, holidays, year): - new_holidays = [] - holiday_lookup = [x[0] for x in holidays] - exceptions = [ - date(year, month, day) for month, day in self.shift_exceptions - ] - - # For each holiday available: - # * if it falls on SUN, add the observed on MON - # * if it falls on SAT, add the observed on FRI - for day, label in holidays: - # ... except if it's been explicitely excepted. - if day in exceptions: - continue - if day.weekday() == SAT: - new_holidays.append((day - timedelta(days=1), - label + " (Observed)")) - elif day.weekday() == SUN: - new_holidays.append((day + timedelta(days=1), - label + " (Observed)")) - - # If year+1 January the 1st is on SAT, add the FRI before to observed - if date(year + 1, 1, 1).weekday() == SAT: - new_holidays.append((date(year, 12, 31,), - "New Years Day (Observed)")) - - # Special rules for XMas and XMas Eve - christmas = date(year, 12, 25) - christmas_eve = date(year, 12, 24) - # Is XMas eve in your calendar? - if christmas_eve in holiday_lookup: - # You are observing the THU before, as an extra XMas Eve - if christmas.weekday() == SAT: - new_holidays.append((date(year, 12, 23), - "Christmas Eve (Observed)")) - # You are observing the 26th (TUE) and 27th (WED) - elif christmas.weekday() == MON: - new_holidays.append((date(year, 12, 26), - "Christmas Eve (Observed)")) - new_holidays.append((date(year, 12, 27), - "Christmas Day (Observed)")) - return holidays + new_holidays - @staticmethod def is_presidential_year(year): return (year % 4) == 0 @@ -146,31 +105,25 @@ def get_confederate_day(self, year): return (day, "Confederate Memorial Day") def get_martin_luther_king_date(self, year): - """ - Martin Luther King is on 3rd MON of January, starting of 1985. - - """ if year < 1985: raise ValueError( "Martin Luther King Day became a holiday in 1985" ) - return UnitedStates.get_nth_weekday_in_month(year, 1, MON, 3) + return date(year, 1, 1) + rd.relativedelta(weekday=rd.MO(3)) def get_martin_luther_king_day(self, year): - """ - Return holiday record for Martin Luther King Jr. Day. - """ - day = self.get_martin_luther_king_date(year) - return (day, self.martin_luther_king_label) + return Holiday( + self.get_martin_luther_king_date(year), + self.martin_luther_king_label, + indication="3rd Monday in January", + ) def get_presidents_day(self, year): - """ - Presidents Day is on the 3rd MON of February - - May be called Washington's or Lincoln's birthday - """ - day = UnitedStates.get_nth_weekday_in_month(year, 2, MON, 3) - return (day, self.presidents_day_label) + return Holiday( + date(year, 2, 1) + rd.relativedelta(weekday=rd.MO(3)), + self.presidents_day_label, + indication="3rd Monday in February", + ) def get_cesar_chavez_days(self, year): """ @@ -210,13 +163,11 @@ def get_washington_birthday_december(self, year): return (day, self.label_washington_birthday_december) def get_columbus_day(self, year): - """ - Columbus day is on the 2nd MON of October. - - Only half of the states recognize it. - """ - day = UnitedStates.get_nth_weekday_in_month(year, 10, MON, 2) - return (day, self.columbus_day_label) + return Holiday( + date(year, 10, 1) + rd.relativedelta(weekday=rd.MO(2)), + self.columbus_day_label, + indication="2nd Monday in October", + ) def get_lincoln_birthday(self, year): """ @@ -246,12 +197,10 @@ def get_inauguration_date(self, year): return inauguration_day def get_national_memorial_day(self, year): - """ - Return National Memorial Day - """ - return ( - UnitedStates.get_last_weekday_in_month(year, 5, MON), - self.national_memorial_day_label + return Holiday( + date(year, 5, 31) + rd.relativedelta(weekday=rd.MO(-1)), + self.national_memorial_day_label, + indication="Last Monday in May", ) def get_mardi_gras(self, year): @@ -265,18 +214,26 @@ def get_variable_days(self, year): # usual variable days days = super(UnitedStates, self).get_variable_days(year) + days += [ + self.get_veterans_day(year), + self.get_national_memorial_day(year), + Holiday( + date(year, 9, 1) + rd.relativedelta(weekday=rd.MO(1)), + "Labor Day", + indication="1st Monday in September", + ), + + Holiday( + date(year, 11, 1) + rd.relativedelta(weekday=rd.TH(4)), + "Thanksgiving Day", + indication="4th Thursday in November", + ), + ] + # Martin Luther King's Day started only in 1985 if year >= 1985: days.append(self.get_martin_luther_king_day(year)) - days.extend([ - self.get_national_memorial_day(year), - (UnitedStates.get_nth_weekday_in_month(year, 9, MON), - "Labor Day"), - (UnitedStates.get_nth_weekday_in_month(year, 11, THU, 4), - "Thanksgiving Day"), - ]) - if self.include_mardi_gras: days.append(self.get_mardi_gras(year)) @@ -298,11 +255,16 @@ def get_variable_days(self, year): if self.include_confederation_day: days.append(self.get_confederate_day(year)) + ind = "January 20 (or 21st if Sunday) following an election year" if self.include_inauguration_day: # Is it a "Inauguration year"? if UnitedStates.is_presidential_year(year - 1): days.append( - (self.get_inauguration_date(year), "Inauguration Day") + Holiday( + self.get_inauguration_date(year), + "Inauguration Day", + indication=ind, + ), ) if self.include_election_day_every_year: @@ -319,22 +281,8 @@ def get_variable_days(self, year): return days def get_veterans_day(self, year): - """ - Return Veterans Day (November 11th). - - Placed here because some States are renaming it. - """ - return (date(year, 11, 11), self.veterans_day_label) - - def get_fixed_holidays(self, year): - days = super(UnitedStates, self).get_fixed_holidays(year) - days.append(self.get_veterans_day(year)) - return days - - def get_calendar_holidays(self, year): - """ - Will return holidays and their shifted days - """ - days = super(UnitedStates, self).get_calendar_holidays(year) - days = self.shift(days, year) - return days + return Holiday( + date(year, 11, 11), + self.veterans_day_label, + indication='Nov 11', + ) From bc949ecaeadf0d2a201571f9a5d9d3a472a0c0c0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 16:21:13 -0500 Subject: [PATCH 188/447] Alaska day is observed on the nearest weekday. --- calendra/usa/alaska.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/calendra/usa/alaska.py b/calendra/usa/alaska.py index 2a899ad3..dd51c066 100644 --- a/calendra/usa/alaska.py +++ b/calendra/usa/alaska.py @@ -2,14 +2,20 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) +import datetime + from .core import UnitedStates -from ..core import MON +from ..core import MON, Holiday class Alaska(UnitedStates): """Alaska""" FIXED_HOLIDAYS = UnitedStates.FIXED_HOLIDAYS + ( - (10, 18, 'Alaska Day'), + Holiday( + datetime.date(2000, 10, 18), + 'Alaska Day', + observance_shift=Holiday.nearest_weekday, + ), ) include_columbus_day = False From 3e7dbabdd45634f448c8f0f01873315c16eff64a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 17:18:58 -0500 Subject: [PATCH 189/447] Use 'nearest weekday' for observance shift in UnitedStates. --- calendra/tests/test_usa.py | 27 +++++++++++++++++---------- calendra/usa/alaska.py | 1 - calendra/usa/core.py | 5 ++++- pytest.ini | 2 +- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/calendra/tests/test_usa.py b/calendra/tests/test_usa.py index 6d29dbd5..26ebd25e 100644 --- a/calendra/tests/test_usa.py +++ b/calendra/tests/test_usa.py @@ -427,7 +427,8 @@ def test_state_year_2015(self): def test_christmas_2016(self): holidays = self.cal.holidays_set(2016) self.assertIn(date(2016, 12, 24), holidays) # XMas Eve - self.assertIn(date(2016, 12, 23), holidays) # XMas Eve shifted + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2016, 12, 23), observed) # XMas Eve shifted def test_president_day_label(self): # Overwrite UnitedStatesTest.test_president_day_label @@ -513,7 +514,8 @@ def test_state_year_2014(self): def test_state_year_2015(self): holidays = self.cal.holidays_set(2015) - self.assertIn(date(2015, 7, 3), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2015, 7, 3), observed) self.assertIn(date(2015, 11, 27), holidays) # Thanksgiving Friday def test_thanksgiving_friday_label(self): @@ -573,13 +575,14 @@ class HawaiiTest(ElectionDayEvenYears, NoColumbus, UnitedStatesTest): def test_state_year_2017(self): holidays = self.cal.holidays_set(2017) + observed = set(map(self.cal.get_observed_date, holidays)) self.assertIn(date(2017, 3, 26), holidays) # Prince Jonah Kuhio Kalanianaole self.assertIn(date(2017, 3, 27), - holidays) # Prince Jonah Kuhio Kalanianaole (shifted) + observed) # Prince Jonah Kuhio Kalanianaole (shifted) self.assertIn(date(2017, 4, 14), holidays) # Good Friday self.assertIn(date(2017, 6, 11), holidays) # Kamehameha - self.assertIn(date(2017, 6, 12), holidays) # Kamehameha (shifted) + self.assertIn(date(2017, 6, 12), observed) # Kamehameha (shifted) self.assertIn(date(2017, 8, 18), holidays) # Statehood day def test_state_year_2018(self): @@ -705,7 +708,8 @@ def test_state_year_2014(self): def test_state_year_2015(self): holidays = self.cal.holidays_set(2015) - self.assertIn(date(2015, 7, 3), holidays) + observed = set(map(self.cal.get_observed_date, holidays)) + self.assertIn(date(2015, 7, 3), observed) self.assertIn(date(2015, 11, 27), holidays) # Thanksgiving Friday @@ -1108,14 +1112,15 @@ class SouthCarolinaTest(NoColumbus, UnitedStatesTest): def test_state_year_2014(self): holidays = self.cal.holidays_set(2014) + observed = set(map(self.cal.get_observed_date, holidays)) # Confederate Memorial Day self.assertIn(date(2014, 5, 10), holidays) # Observed here, it falls on SAT - self.assertIn(date(2014, 5, 9), holidays) + self.assertIn(date(2014, 5, 9), observed) self.assertIn(date(2014, 11, 28), holidays) # Thanksgiving Friday - self.assertIn(date(2014, 12, 24), holidays) # XMas Eve - self.assertIn(date(2014, 12, 26), holidays) # Boxing day + self.assertIn(date(2014, 12, 24), observed) # XMas Eve + self.assertIn(date(2014, 12, 26), observed) # Boxing day def test_state_year_2015(self): holidays = self.cal.holidays_set(2015) @@ -1211,15 +1216,17 @@ class VermontTest(NoColumbus, UnitedStatesTest): def test_state_year_2014(self): holidays = self.cal.holidays_set(2014) + observed = set(map(self.cal.get_observed_date, holidays)) self.assertIn(date(2014, 3, 4), holidays) # Town Meeting Day self.assertIn(date(2014, 8, 16), holidays) # Bennington Battle Day - self.assertIn(date(2014, 8, 15), holidays) # Shifted to FRI + self.assertIn(date(2014, 8, 15), observed) # Shifted to FRI def test_state_year_2015(self): holidays = self.cal.holidays_set(2015) + observed = set(map(self.cal.get_observed_date, holidays)) self.assertIn(date(2015, 3, 3), holidays) # Town Meeting Day self.assertIn(date(2015, 8, 16), holidays) # Bennington Battle Day - self.assertIn(date(2015, 8, 17), holidays) # Shifted to MON + self.assertIn(date(2015, 8, 17), observed) # Shifted to MON class VirginiaTest(UnitedStatesTest): diff --git a/calendra/usa/alaska.py b/calendra/usa/alaska.py index dd51c066..501adc54 100644 --- a/calendra/usa/alaska.py +++ b/calendra/usa/alaska.py @@ -14,7 +14,6 @@ class Alaska(UnitedStates): Holiday( datetime.date(2000, 10, 18), 'Alaska Day', - observance_shift=Holiday.nearest_weekday, ), ) include_columbus_day = False diff --git a/calendra/usa/core.py b/calendra/usa/core.py index 5c0f7a51..2e013a95 100644 --- a/calendra/usa/core.py +++ b/calendra/usa/core.py @@ -18,10 +18,13 @@ class UnitedStates(WesternCalendar, ChristianMixin): date(2000, 7, 4), 'Independence Day', indication='July 4', - observance_shift=Holiday.nearest_weekday, ), ) + @property + def observance_shift(self): + return Holiday.nearest_weekday + # Veterans day label veterans_day_label = 'Veterans Day' diff --git a/pytest.ini b/pytest.ini index 640ed5bc..846ca67a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] norecursedirs=dist build .tox -addopts=--cov calendra --doctest-modules +#addopts=--cov calendra --doctest-modules doctest_optionflags=ALLOW_UNICODE ELLIPSIS From f122100bc8871caaeb1bb4cd226cf4dc64e816a8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 18:40:28 -0500 Subject: [PATCH 190/447] Include pygments so checkdocs passes. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e4d45668..259f4c2c 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ 'pytest>=2.8', 'pytest-sugar', 'collective.checkdocs', + 'pygments', 'pytest-cov', 'pytest-pep8', ], From 9a2d872c50cbb862e1adb9a375098de58e04de15 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 19:23:55 -0500 Subject: [PATCH 191/447] Link to workalendar issues in changelog. --- docs/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 14744ee8..71495533 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,6 +27,10 @@ pattern=r'PEP[- ](?P\d+)', url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/', ), + dict( + pattern=r'\(#(?P\d+)(.*?)\)', + url='{GH}/peopledoc/workalendar/issues/{wk_issue}', + ), ], ), } From dd475b7ad1784a564929c711781eebd9b85ba063 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 19:32:19 -0500 Subject: [PATCH 192/447] Use https --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index bcdb31e5..043561df 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ .. image:: https://img.shields.io/pypi/pyversions/skeleton.svg .. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg - :target: http://travis-ci.org/jaraco/skeleton + :target: https://travis-ci.org/jaraco/skeleton .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest - :target: http://skeleton.readthedocs.io/en/latest/?badge=latest + :target: https://skeleton.readthedocs.io/en/latest/?badge=latest From 97c492d425da509f96f554a66499d13723d147bf Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 19:32:38 -0500 Subject: [PATCH 193/447] Add build-docs env in tox. --- tox.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tox.ini b/tox.ini index 16bf78a7..c3b6d2e6 100644 --- a/tox.ini +++ b/tox.ini @@ -7,3 +7,11 @@ commands = python setup.py checkdocs usedevelop = True extras = testing + +[testenv:build-docs] +extras = + docs + testing +changedir = docs +commands = + python -m sphinx . {toxinidir}/build/html From b1e473acb2d4b197b810faef86b58bfb9dfac79c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 19:40:18 -0500 Subject: [PATCH 194/447] Clean up extraneous details in changelog. --- CHANGES.rst | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f7332232..a2d7ca46 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,3 @@ -CHANGELOG -========= - 2.0 --- @@ -131,8 +128,8 @@ Incorporate changes from workalendar 0.4.0: - Added Slovakia calendar, thx to @Adman - Fixed the Boxing day & boxing day shift for Australia -1.4 (2015-04-14) ----------------- +1.4 +--- ``Calendar.get_observed_date`` now allows ``observance_shift`` to be a callable accepting the holiday and calendar and returning the observed @@ -141,8 +138,8 @@ locating the nearest weekday. - #5: USA Independence Day now honors the nearest weekday model. -1.3 (2015-02-15) ----------------- +1.3 +--- Incorporate these fixes from Workalendar 0.3: @@ -151,13 +148,13 @@ Incorporate these fixes from Workalendar 0.3: - BUGFIX: Renaming Showa Day. "ō is not romji" #100 (thx @shinriyo) - BUGFIX: Belgian National Day title #99 (thx @laulaz) -1.2.1 (2015-02-15) ------------------- +1.2.1 +----- Correct usage in example. -1.2 (2015-01-07) ----------------- +1.2 +--- Fixed issue #4 where Finland holidays were shifted but shouldn't have been. Calendars and Holidays may now specify observance_shift=None to signal no @@ -166,19 +163,19 @@ shift. Package can now be tested with pytest-runner by invoking ``python setup.py pytest``. -1.1.3 (2014-12-29) ------------------- +1.1.3 +----- Fix name of Finnish Independence Day. -1.1.2 (2014-11-07) ------------------- +1.1.2 +----- Fixed issues with packaging (disabled installation an zip egg and now use setuptools always). -1.1 (2014-11-07) ----------------- +1.1 +--- UnitedKingdom Calendar now uses indicated/observed Holidays. @@ -187,8 +184,8 @@ Includes these changes slated for workalendar 0.3: - BUGFIX: shifting UK boxing day if Christmas day falls on a Friday (shit to next Monday) #95 -1.0 (2014-09-21) ----------------- +1.0 +--- Initial release of Calendra based on Workalendar 0.2. @@ -205,8 +202,8 @@ Includes these changes slated for workalendar 0.3: - little improvement to directly return a tested value. -0.2.0 (2014-07-15) ------------------- +0.2.0 +----- - How to contribute documentation, - Added Belgium, European Central Bank, Sweden, every specific calendar in the @@ -216,8 +213,8 @@ Includes these changes slated for workalendar 0.3: default and should be set to "True" when needed. -0.1 (2014-02-17) ----------------- +0.1 +--- - added LunarCalendar, including lunar month calculations - added SouthKoreanCalendar, for a LunarCalendar proof of concept @@ -231,8 +228,8 @@ Includes these changes slated for workalendar 0.3: of JapanCalendar. -v0.0.1 (2013-11-21) -------------------- +v0.0.1 +------ - First released version - Core calendar classes, Western (European and North American) From 17d54b86aa5edc79b03eb71e7363076fe53be979 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 19:49:47 -0500 Subject: [PATCH 195/447] Link to workalendar changelog. --- docs/conf.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 71495533..2891f088 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,6 +13,7 @@ '../CHANGES.rst': dict( using=dict( GH='https://github.com', + workalendar='https://github.com/peopledoc/workalendar/', ), replace=[ dict( @@ -29,7 +30,11 @@ ), dict( pattern=r'\(#(?P\d+)(.*?)\)', - url='{GH}/peopledoc/workalendar/issues/{wk_issue}', + url='{workalendar}issues/{wk_issue}', + ), + dict( + pattern=r'(?P[Ww]orkalendar \d+\.\d+(\.\d+)?)', + url='{workalendar}blob/master/CHANGELOG', ), ], ), From 3fc990d5b99cb32cc141a66939ef9fdbe5f8ede4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 19:54:36 -0500 Subject: [PATCH 196/447] Reference individual workalendar releases. --- CHANGES.rst | 50 ++++++++++++-------------------------------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index a2d7ca46..77193048 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,13 +1,15 @@ 2.0 --- -Incorporate changes from workalendar 2.3.1: +Incorporate changes from workalendar 2.1.0: - Added Hong Kong, by @nedlowe (#235). - Splitted `africa.py` file into an `africa/` module (#236). - Added Alabama Counties - Baldwin County, Mobile County, Perry County. Refactored UnitedStates classes to have a parameter to include the "Mardi Gras" day (#214). - Added brazilian calendar to consider working days for bank transactions, by @fvlima (#238). +Incorporate changes from workalendar 2.0.0: + - Major refactor in the USA module. Each State is now an independant module, all of the Mixins were removed, all the possible corrections have been made, following the main Wikipedia page, and cross-checking with official sources when it was possible (#171). - Added District of Columbia in the USA module (#217). - Run tests with Python3.6 in CI (#210) @@ -15,6 +17,8 @@ Incorporate changes from workalendar 2.3.1: - Various refactors for the Asia module, essentially centered around a more convenient Chinese New Year computation toolset (#202). - Refactoring the USA tests: using inheritance to test federal and state-based holidays using only one "Don't Repeat Yourself" codebase (#213). +Incorporate changes from workalendar 1.3.0: + - Added Singapore calendar, initiated by @nedlowe (#194 + #195). - Added Malaysia, by @gregyhj (#201). - Added Good Friday in the list of Hungarian holidays, as of the year 2017 (#203), thx to @mariusz-korzekwa for the bug report. @@ -22,12 +26,16 @@ Incorporate changes from workalendar 2.3.1: - Fixed a bug in Slovakia calendar, de-duplicated Christmas Day, that appeared twice (#205). - Fixed important bugs in the calendars of the following Brazilian cities: Vitória, Vila Velha, Cariacica, Guarapari and Serra - thx to Fernanda Gonçalves Rodrigues, who confirmed this issue raised by @Skippern (#199). +Incorporate changes from workalendar 1.2.0: + - Moved all the calendar of countries on the american continent in their own modules (#188). - Refactor base Calendar class get_weekend_days to use WEEKEND_DAYS more intelligently (#191 + #192). - Many additions to the Brazil and various states / cities. Were added: Acre, Alagoas, Amapá, Amazonas, Bahia, Ceará, Distrito Federal, Espírito Santo State, Goiás, Maranhão, Mato Grosso, Mato Grosso do Sul, Pará, Paraíba, Pernambuco, Piauí, Rio de Janeiro, Rio Grande do Norte, Rio Grande do Sul, Rondônia, Roraima, Santa Catarina, São Paulo, Sergipe, Tocantins, City of Vitória, City of Vila Velha, City of Cariacica, City of Guarapari and City of Serra (#187). - Added a ``good_friday_label`` class variable to ``ChristianMixin`` ; one can assign the right label to this holiday (#187). - Added a ``ash_wednesday_label`` class variable to ``ChristianMixin`` ; one can assign the right label to this holiday (#187). +Incorporate changes from workalendar 1.1.0: + - Added Cyprus. thx @gregn610 (#174). - Added Latvia. thx @gregn610 (#178). - Added Malta. thx @gregn610 (#179). @@ -38,7 +46,7 @@ Incorporate changes from workalendar 2.3.1: - Fixed Historical and one-off holidays for South Africa. thx @gregn610 (#173). - Minor PEP8 fixes (#186). -After several years of development, we can now say that this library is production-ready, so we're releasing its 1.0.0 version. Millions of "thank you" to all the contributors involved. +Incorporate changes from workalendar 1.0.0: - Add Ireland. thx @gregn610 (#152). - Bugfix: New Year's Eve is not a holiday in Netherlands (#154). @@ -46,6 +54,8 @@ After several years of development, we can now say that this library is producti - Add Bulgaria. thx @gregn610 (#156) - Add Croatia. thx @gregn610 (#157) +Incorporate changes from workalendar 0.8.1: + - Reformation Day is a national holiday in Germany, but only in 2017 (#150). 1.8 @@ -200,39 +210,3 @@ Includes these changes slated for workalendar 0.3: - Germany calendar added, thx to @rndusr - Support building on systems where LANG=C (Ubuntu) #92 - little improvement to directly return a tested value. - - -0.2.0 ------ - -- How to contribute documentation, -- Added Belgium, European Central Bank, Sweden, every specific calendar in the - United States of America, Canada. -- BUGFIX: fixed a corpus christi bug. This day used to be included in every - ChristianMixin calendar, except noticed otherwise. Now it's not included by - default and should be set to "True" when needed. - - -0.1 ---- - -- added LunarCalendar, including lunar month calculations -- added SouthKoreanCalendar, for a LunarCalendar proof of concept -- added Python3 support -- added Algeria, Australia, Brazil, Chile, Czech Republic, Finland, - France Alsace-Moselle, Greece, Hungary, Iceland, Italy, Ivory Coast, Japan, - Madagascar, Marshall Islands, Mexico, Northern Ireland, Norway, Panama, - Poland, Qatar, South Africa, São Tomé, Taiwan, United Kingdom calendars. -- BACKWARDS INCOMPATIBILITY: calendar suffix for class names are now obsolete. - e.g: to use the Japan calendar, simply import `workalendar.asia.Japan` instead - of JapanCalendar. - - -v0.0.1 ------- - -- First released version -- Core calendar classes, Western (European and North American) - easter computations, -- United States federal days -- France legal holidays days From 8e10aa2cbb9164341171b48c74db01d4531818ca Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 20:16:39 -0500 Subject: [PATCH 197/447] Enclose more workalendar references in parenthesis for consistency. --- CHANGES.rst | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 77193048..23f01010 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -155,8 +155,8 @@ Incorporate these fixes from Workalendar 0.3: - ``delta`` argument for ``add_working_days()`` can be negative. added a ``sub_working_days()`` method that computes working days backwards. -- BUGFIX: Renaming Showa Day. "ō is not romji" #100 (thx @shinriyo) -- BUGFIX: Belgian National Day title #99 (thx @laulaz) +- BUGFIX: Renaming Showa Day. "ō is not romji" (#100) (thx @shinriyo) +- BUGFIX: Belgian National Day title (#99) (thx @laulaz) 1.2.1 ----- @@ -191,16 +191,15 @@ UnitedKingdom Calendar now uses indicated/observed Holidays. Includes these changes slated for workalendar 0.3: -- BUGFIX: shifting UK boxing day if Christmas day falls on a Friday (shit to - next Monday) #95 +- BUGFIX: shifting UK boxing day if Christmas day falls on a Friday (shift to + next Monday) (#95) 1.0 --- Initial release of Calendra based on Workalendar 0.2. -- Adds Holiday class per `Workalendar Pull Request #72 - `_. Adds support for giving +- Adds Holiday class per (#72). Adds support for giving holidays a more rich description and better resolution of observed versus indicated holidays. See the pull request for detail on the motivation and implementation. See the usa.UnitedStates calendar for example usage. @@ -208,5 +207,5 @@ Initial release of Calendra based on Workalendar 0.2. Includes these changes slated for workalendar 0.3: - Germany calendar added, thx to @rndusr -- Support building on systems where LANG=C (Ubuntu) #92 +- Support building on systems where LANG=C (Ubuntu) (#92) - little improvement to directly return a tested value. From 08f138f99a79eaed0564e12213992938223c9325 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 2 Jan 2018 20:20:18 -0500 Subject: [PATCH 198/447] Fix PR reference. --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 23f01010..d234cd97 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -199,7 +199,7 @@ Includes these changes slated for workalendar 0.3: Initial release of Calendra based on Workalendar 0.2. -- Adds Holiday class per (#72). Adds support for giving +- Adds Holiday class per (#79). Adds support for giving holidays a more rich description and better resolution of observed versus indicated holidays. See the pull request for detail on the motivation and implementation. See the usa.UnitedStates calendar for example usage. From 418565c78c8076f396296cf8f2137adaafff047b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 3 Jan 2018 08:48:23 -0500 Subject: [PATCH 199/447] Update readme to present advantages of Calendra. --- README.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.rst b/README.rst index 46074626..abd7d82b 100644 --- a/README.rst +++ b/README.rst @@ -24,6 +24,16 @@ designed to be more extensible and introspectable, adding interfaces where `Workalendar is philosophically opposed for the sake of simplicity `_. +What can Calendra do that Workalendar cannot? + +- Provides descriptions for holidays for the "day indicated" for each + Holiday (such as '3rd Monday in August'). +- Keeps distinct the indicated and observed dates for Holidays, such + that it's possible to determine on what day a given holiday is observed. +- Allows the number of Holidays in a calendar year to be counted. +- Consolidates observance logic in the core code rather than requiring + each calendar implementation to implement its own. + Status ====== From 63659653697e9037ed5cb5a770dc00b07c77e5a9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 3 Jan 2018 15:34:05 -0500 Subject: [PATCH 200/447] Run only default environment by default. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index c3b6d2e6..7cccd421 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,5 @@ [tox] +envlist = python minversion = 2.4 [testenv] From 99d850f0ed9852993626d4869e9f096e1643be6d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 17 Jan 2018 09:55:12 -0500 Subject: [PATCH 201/447] To support namespace packages, Setuptools must be 31.0.1. This change is necessary with the adoption of tox-venv, which uses Python's venv, which does not install the latest setuptools by default. --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 7cccd421..df1b0eff 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,8 @@ envlist = python minversion = 2.4 [testenv] +deps = + setuptools>=31.0.1 commands = py.test {posargs} python setup.py checkdocs From bbc018de3cbee4bccdcbb58b637c0c659e6e37e1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 26 Jan 2018 11:59:01 -0500 Subject: [PATCH 202/447] Need to avoid .eggs in recursing dirs. Ref pypa/setuptools_scm#212. --- pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 56a87745..1b2f6247 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] -norecursedirs=dist build .tox +norecursedirs=dist build .tox .eggs addopts=--doctest-modules doctest_optionflags=ALLOW_UNICODE ELLIPSIS From b9aba822c7e154d1ad185585fe7642947b3c5265 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 29 Jan 2018 09:06:00 -0500 Subject: [PATCH 203/447] Use tox-venv for future compatibility. --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 61180dba..85540ec0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,6 @@ jobs: cache: pip install: -- pip install tox +- pip install tox tox-venv script: tox diff --git a/appveyor.yml b/appveyor.yml index 0a8ce5c5..3d55a92b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,7 +15,7 @@ install: build: off test_script: - - "python -m pip install tox" + - "python -m pip install tox tox-venv" - "tox" version: '{build}' From 34ab781f8768ab5101f087cfffe7e38b94048a7f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Feb 2018 09:28:53 -0500 Subject: [PATCH 204/447] Disable pytest-sugar until Frozenball/pytest-sugar#133 is addressed. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 75f23712..e07ba771 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ extras_require={ 'testing': [ 'pytest>=2.8', - 'pytest-sugar', + # 'pytest-sugar', 'collective.checkdocs', ], 'docs': [ From 3902aabd7f5c4cb0f4aba8d2785da98e87cb7d6a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 Feb 2018 13:24:23 -0500 Subject: [PATCH 205/447] Bring back pytest-sugar with a minimum version to support Pytest 3.4. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e07ba771..9e73e231 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ extras_require={ 'testing': [ 'pytest>=2.8', - # 'pytest-sugar', + 'pytest-sugar>=0.9.1', 'collective.checkdocs', ], 'docs': [ From a8f66602f22459be95f8463e8cf6de1e653b352c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 20 Feb 2018 08:45:54 -0500 Subject: [PATCH 206/447] Save the pip cache across builds. Ref pypa/setuptools#1279. --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 3d55a92b..2b7808f9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,6 +14,9 @@ install: build: off +cache: + - '%LOCALAPPDATA%\pip\Cache' + test_script: - "python -m pip install tox tox-venv" - "tox" From 41b814aa6cff3c46788a1d410095061a82af2076 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 28 Feb 2018 09:43:16 -0500 Subject: [PATCH 207/447] Add workaround for build failures on Python 3.7 (yaml/pyyaml#126). --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index df1b0eff..47eae514 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,8 @@ minversion = 2.4 [testenv] deps = setuptools>=31.0.1 + # workaround for yaml/pyyaml#126 + # git+https://github.com/yaml/pyyaml@master#egg=pyyaml commands = py.test {posargs} python setup.py checkdocs From 40da2c65007ccc250c7d897d497ef2b3fb58f3d4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 3 Mar 2018 13:33:07 -0500 Subject: [PATCH 208/447] Run flake8 with tests. Add flake8 config to ignore common exclusions. Add comments to testing and docs extras to aid with merges. --- .flake8 | 2 ++ pytest.ini | 2 +- setup.py | 7 +++++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..e9955e71 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +ignore = W191,W503 diff --git a/pytest.ini b/pytest.ini index 1b2f6247..0ba22c33 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] norecursedirs=dist build .tox .eggs -addopts=--doctest-modules +addopts=--doctest-modules --flake8 doctest_optionflags=ALLOW_UNICODE ELLIPSIS diff --git a/setup.py b/setup.py index 9e73e231..c5ad4b1b 100644 --- a/setup.py +++ b/setup.py @@ -36,14 +36,21 @@ ], extras_require={ 'testing': [ + # upstream 'pytest>=2.8', 'pytest-sugar>=0.9.1', 'collective.checkdocs', + 'pytest-flake8', + + # local ], 'docs': [ + # upstream 'sphinx', 'jaraco.packaging>=3.2', 'rst.linker>=1.9', + + # local ], }, setup_requires=[ From d5d22342cfd4bd0ebebefa98765ae1dfc5770bb2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 5 Mar 2018 09:50:36 -0500 Subject: [PATCH 209/447] Add appveyor badge (commented). Disable RTD by default. --- README.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 043561df..78750be7 100644 --- a/README.rst +++ b/README.rst @@ -6,5 +6,8 @@ .. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg :target: https://travis-ci.org/jaraco/skeleton -.. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest - :target: https://skeleton.readthedocs.io/en/latest/?badge=latest +.. .. image:: https://img.shields.io/appveyor/ci/jaraco/skeleton/master.svg +.. :target: https://ci.appveyor.com/project/jaraco/skeleton/branch/master + +.. .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest +.. :target: https://skeleton.readthedocs.io/en/latest/?badge=latest From cd31f81d564c1cd93c7cbf0b1b19c034e310ad52 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Mar 2018 17:28:14 -0500 Subject: [PATCH 210/447] Limit workaround to affected Python --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 47eae514..c2bac458 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ minversion = 2.4 deps = setuptools>=31.0.1 # workaround for yaml/pyyaml#126 - # git+https://github.com/yaml/pyyaml@master#egg=pyyaml + # git+https://github.com/yaml/pyyaml@master#egg=pyyaml;python_version=="3.7" commands = py.test {posargs} python setup.py checkdocs From 021b18b89cf83977397350ebe54603032086baf6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 27 Mar 2018 15:57:25 -0400 Subject: [PATCH 211/447] Bump minimum pytest version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c5ad4b1b..62211249 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ extras_require={ 'testing': [ # upstream - 'pytest>=2.8', + 'pytest>=3.5', 'pytest-sugar>=0.9.1', 'collective.checkdocs', 'pytest-flake8', From e302df43fc90a3db2bc9119c9e0dad08a754c0f5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 10 Apr 2018 09:19:48 -0400 Subject: [PATCH 212/447] Add pyproject.toml declaring build dependencies. --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..3ef243cc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +requires = ["setuptools", "wheel", "setuptools_scm>=1.15"] From 9f06de212eb53c35ea52781796c58761fcd06de3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 12 Apr 2018 11:36:04 -0400 Subject: [PATCH 213/447] When ignoring linter warnings, document the reason. --- .flake8 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index e9955e71..df5f5271 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,6 @@ [flake8] -ignore = W191,W503 +ignore = + # Allow tabs for indentation + W191 + # W503 violates spec https://github.com/PyCQA/pycodestyle/issues/513 + W503 From 53c017416d74f96c49fde361c0a5b774ceac00c6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 9 Jun 2018 15:31:39 -0400 Subject: [PATCH 214/447] Disable the (broken) IPv6 in Travis. Ref travis-ci/travis-ci#8361. --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 85540ec0..e22ab6ff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,4 +31,9 @@ cache: pip install: - pip install tox tox-venv +before_script: + # Disable IPv6. Ref travis-ci/travis-ci#8361 + - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then + sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'; + fi script: tox From a4bdfe7caddd6b1bf4149f2d02adee727168ff8a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 11 Jun 2018 08:59:18 -0400 Subject: [PATCH 215/447] Don't match issues if preceeded by some other indicator. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 14744ee8..aeda56c0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ ), replace=[ dict( - pattern=r'(Issue )?#(?P\d+)', + pattern=r'(Issue #|\B#)(?P\d+)', url='{package_url}/issues/{issue}', ), dict( From 7c9ad053c3ef0b66cc71431fb619da8e3a12bc26 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 14 Jun 2018 08:25:24 -0400 Subject: [PATCH 216/447] skip_upload_docs is default --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e22ab6ff..2560fc39 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,6 @@ jobs: secure: ... # encrypt password with `travis encrypt` distributions: dists skip_cleanup: true - skip_upload_docs: true cache: pip From 67c79e3182614e96fd4cf3a4813932b1edeff262 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 14 Jun 2018 12:23:57 -0400 Subject: [PATCH 217/447] Drop the dot; http://blog.pytest.org/2016/whats-new-in-pytest-30/ --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c2bac458..c6c14f07 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ deps = # workaround for yaml/pyyaml#126 # git+https://github.com/yaml/pyyaml@master#egg=pyyaml;python_version=="3.7" commands = - py.test {posargs} + pytest {posargs} python setup.py checkdocs usedevelop = True extras = testing From 440adac3c3f91519a1ff47114774dbd1d5baf676 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 14 Jun 2018 15:09:33 -0400 Subject: [PATCH 218/447] Rely on declarative config to create long_description. --- pyproject.toml | 2 +- setup.cfg | 1 + setup.py | 6 ------ 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3ef243cc..1af54cbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,2 @@ [build-system] -requires = ["setuptools", "wheel", "setuptools_scm>=1.15"] +requires = ["setuptools>=30.3", "wheel", "setuptools_scm>=1.15"] diff --git a/setup.cfg b/setup.cfg index 378a8e4f..ff90351b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,3 +7,4 @@ universal = 1 [metadata] license_file = LICENSE +long_description = file:README.rst diff --git a/setup.py b/setup.py index 62211249..4afc6282 100644 --- a/setup.py +++ b/setup.py @@ -2,13 +2,8 @@ # Project skeleton maintained at https://github.com/jaraco/skeleton -import io - import setuptools -with io.open('README.rst', encoding='utf-8') as readme: - long_description = readme.read() - name = 'skeleton' description = '' nspkg_technique = 'native' @@ -23,7 +18,6 @@ author="Jason R. Coombs", author_email="jaraco@jaraco.com", description=description or name, - long_description=long_description, url="https://github.com/jaraco/" + name, packages=setuptools.find_packages(), include_package_data=True, From 15024f12b5d4e90aee4f9a780efa263f47865d96 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 27 Jun 2018 21:34:07 -0400 Subject: [PATCH 219/447] Remove workaround for pyyaml 126. --- tox.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tox.ini b/tox.ini index c6c14f07..41e20a33 100644 --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,6 @@ minversion = 2.4 [testenv] deps = setuptools>=31.0.1 - # workaround for yaml/pyyaml#126 - # git+https://github.com/yaml/pyyaml@master#egg=pyyaml;python_version=="3.7" commands = pytest {posargs} python setup.py checkdocs From f8462db925cbfa2ca0721c84376c23026633a730 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 3 Jul 2018 11:13:00 -0400 Subject: [PATCH 220/447] Revert "Remove workaround for pyyaml 126." This reverts commit 15024f12b5d4e90aee4f9a780efa263f47865d96. --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 41e20a33..c6c14f07 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,8 @@ minversion = 2.4 [testenv] deps = setuptools>=31.0.1 + # workaround for yaml/pyyaml#126 + # git+https://github.com/yaml/pyyaml@master#egg=pyyaml;python_version=="3.7" commands = pytest {posargs} python setup.py checkdocs From ed475c925d31ec146979f12b0ebf1e1021335e31 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 3 Jul 2018 11:15:33 -0400 Subject: [PATCH 221/447] We're getting close, but Python 3.7 still requires a workaround --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c6c14f07..5925d3e1 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ minversion = 2.4 deps = setuptools>=31.0.1 # workaround for yaml/pyyaml#126 - # git+https://github.com/yaml/pyyaml@master#egg=pyyaml;python_version=="3.7" + # pyyaml>=4.2b2;python_version=="3.7" commands = pytest {posargs} python setup.py checkdocs From beb0e0eb774dd15e79574ace338b885101d86d4b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 8 Aug 2018 15:14:55 -0400 Subject: [PATCH 222/447] Use xenial to include support for Python 3.7. --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2560fc39..b54e8e52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,11 @@ -dist: trusty +dist: xenial sudo: false language: python python: - 2.7 -- &latest_py3 3.6 +- 3.6 +- &latest_py3 3.7 jobs: fast_finish: true From 5d245bb8ca22194dbf17e69f7db5f082101f931c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 20 Aug 2018 17:15:48 -0400 Subject: [PATCH 223/447] Remove release, no longer needed. Use twine instead. --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ff90351b..8fdad4fa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,4 @@ [aliases] -release = dists upload dists = clean --all sdist bdist_wheel [bdist_wheel] From bf8c57034ab857eb5b642fbc137a811276e8067a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 20 Aug 2018 17:17:12 -0400 Subject: [PATCH 224/447] Also ignore W504 in flake8, following the indication in OCA/maintainer-quality-tools that neither W503 nor W504 are worthwhile in general. --- .flake8 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.flake8 b/.flake8 index df5f5271..c85d34a7 100644 --- a/.flake8 +++ b/.flake8 @@ -4,3 +4,5 @@ ignore = W191 # W503 violates spec https://github.com/PyCQA/pycodestyle/issues/513 W503 + # W504 has issues https://github.com/OCA/maintainer-quality-tools/issues/545 + W504 From 8cd0459a9012ad5070c5b2364d9835653e6d58b7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 28 Aug 2018 08:54:42 -0400 Subject: [PATCH 225/447] Release of pyyaml 3.13 seems to have fixed install issues on Python 3.7. --- tox.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tox.ini b/tox.ini index 5925d3e1..41e20a33 100644 --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,6 @@ minversion = 2.4 [testenv] deps = setuptools>=31.0.1 - # workaround for yaml/pyyaml#126 - # pyyaml>=4.2b2;python_version=="3.7" commands = pytest {posargs} python setup.py checkdocs From 5633116de34f53c892d1f2c6d0f7de14c965cfa7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 28 Aug 2018 12:59:25 -0400 Subject: [PATCH 226/447] Block pytest 3.7.3 due to pytest-dev/pytest#3888. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4afc6282..ba0fb89a 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ extras_require={ 'testing': [ # upstream - 'pytest>=3.5', + 'pytest>=3.5,!=3.7.3', 'pytest-sugar>=0.9.1', 'collective.checkdocs', 'pytest-flake8', From 9cdf6ef1f4401b1ec4b032f1b61c0f4f7fd78b8d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 17 Sep 2018 10:08:54 -0400 Subject: [PATCH 227/447] Move most package config to declarative config --- setup.cfg | 38 ++++++++++++++++++++++++++++++++++++ setup.py | 58 +------------------------------------------------------ 2 files changed, 39 insertions(+), 57 deletions(-) diff --git a/setup.cfg b/setup.cfg index 8fdad4fa..adaed86d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,4 +6,42 @@ universal = 1 [metadata] license_file = LICENSE +name = skeleton +author = Jason R. Coombs +author_email = jaraco@jaraco.com +description = skeleton long_description = file:README.rst +url = https://github.com/jaraco/skeleton +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + +[options] +packages = find: +include_package_data = true +python_requires = >=2.7 +install_requires = +setup_requires = setuptools_scm >= 1.15.0 + +[options.extras_require] +testing = + # upstream + pytest >= 3.5, !=3.7.3 + pytest-sugar >= 0.9.1 + collective.checkdocs + pytest-flake8 + + # local + +docs = + # upstream + sphinx + jaraco.packaging >= 3.2 + rst.linker >= 1.9 + + # local + +[options.entry_points] diff --git a/setup.py b/setup.py index ba0fb89a..c990c529 100644 --- a/setup.py +++ b/setup.py @@ -4,61 +4,5 @@ import setuptools -name = 'skeleton' -description = '' -nspkg_technique = 'native' -""" -Does this package use "native" namespace packages or -pkg_resources "managed" namespace packages? -""" - -params = dict( - name=name, - use_scm_version=True, - author="Jason R. Coombs", - author_email="jaraco@jaraco.com", - description=description or name, - url="https://github.com/jaraco/" + name, - packages=setuptools.find_packages(), - include_package_data=True, - namespace_packages=( - name.split('.')[:-1] if nspkg_technique == 'managed' - else [] - ), - python_requires='>=2.7', - install_requires=[ - ], - extras_require={ - 'testing': [ - # upstream - 'pytest>=3.5,!=3.7.3', - 'pytest-sugar>=0.9.1', - 'collective.checkdocs', - 'pytest-flake8', - - # local - ], - 'docs': [ - # upstream - 'sphinx', - 'jaraco.packaging>=3.2', - 'rst.linker>=1.9', - - # local - ], - }, - setup_requires=[ - 'setuptools_scm>=1.15.0', - ], - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - ], - entry_points={ - }, -) if __name__ == '__main__': - setuptools.setup(**params) + setuptools.setup(use_scm_version=True) From f8a537f300727a87c1a4a663a5df8b6e05e1020d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 19 Sep 2018 22:31:38 -0400 Subject: [PATCH 228/447] Ignore pycodestyle warning. Seems it's not going to be fixed anytime soon. --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest.ini b/pytest.ini index 0ba22c33..d0ba39d3 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,3 +2,5 @@ norecursedirs=dist build .tox .eggs addopts=--doctest-modules --flake8 doctest_optionflags=ALLOW_UNICODE ELLIPSIS +filterwarnings= + ignore:Possible nested set::pycodestyle:113 From 106c755f83d701003afca33e0ba22c20c3a31c97 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 24 Sep 2018 18:08:59 -0400 Subject: [PATCH 229/447] Also ignore flake8 error --- pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/pytest.ini b/pytest.ini index d0ba39d3..61dab3d4 100644 --- a/pytest.ini +++ b/pytest.ini @@ -4,3 +4,4 @@ addopts=--doctest-modules --flake8 doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= ignore:Possible nested set::pycodestyle:113 + ignore:Using or importing the ABCs::flake8:410 From 59cceeb35bd1005a16bd9985e623b10c82527e58 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 28 Oct 2018 23:24:08 -0400 Subject: [PATCH 230/447] Require setuptools 34.4 to support python_requires in declarative config. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1af54cbd..65d74263 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,2 @@ [build-system] -requires = ["setuptools>=30.3", "wheel", "setuptools_scm>=1.15"] +requires = ["setuptools>=34.4", "wheel", "setuptools_scm>=1.15"] From 166b43e1429fa1b9b467da82109151222719cc20 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 7 Nov 2018 17:47:20 -0500 Subject: [PATCH 231/447] Add workaround for Frozenball/pytest-sugar#159. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 41e20a33..78cc7f9f 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ minversion = 2.4 [testenv] deps = setuptools>=31.0.1 + pytest-sugar-bugfix159 commands = pytest {posargs} python setup.py checkdocs From d0f07a4e7ad465b0935bf85da94b12b9b8cc2e77 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 9 Nov 2018 22:17:36 -0500 Subject: [PATCH 232/447] Add black config, pre-commit including black, check code with black. --- .flake8 | 5 +++-- .pre-commit-config.yaml | 5 +++++ README.rst | 4 ++++ docs/conf.py | 44 ++++++++++++++++++++--------------------- pyproject.toml | 3 +++ pytest.ini | 2 +- setup.cfg | 1 + setup.py | 2 +- 8 files changed, 40 insertions(+), 26 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.flake8 b/.flake8 index c85d34a7..790c109f 100644 --- a/.flake8 +++ b/.flake8 @@ -1,8 +1,9 @@ [flake8] +max-line-length = 88 ignore = - # Allow tabs for indentation - W191 # W503 violates spec https://github.com/PyCQA/pycodestyle/issues/513 W503 # W504 has issues https://github.com/OCA/maintainer-quality-tools/issues/545 W504 + # Black creates whitespace before colon + E203 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..922d9424 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,5 @@ +repos: +- repo: https://github.com/ambv/black + rev: 18.9b0 + hooks: + - id: black diff --git a/README.rst b/README.rst index 78750be7..7050da33 100644 --- a/README.rst +++ b/README.rst @@ -6,6 +6,10 @@ .. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg :target: https://travis-ci.org/jaraco/skeleton +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black + :alt: Code style: Black + .. .. image:: https://img.shields.io/appveyor/ci/jaraco/skeleton/master.svg .. :target: https://ci.appveyor.com/project/jaraco/skeleton/branch/master diff --git a/docs/conf.py b/docs/conf.py index aeda56c0..d9ea1a63 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,31 +2,31 @@ # -*- coding: utf-8 -*- extensions = [ - 'sphinx.ext.autodoc', - 'jaraco.packaging.sphinx', - 'rst.linker', + 'sphinx.ext.autodoc', + 'jaraco.packaging.sphinx', + 'rst.linker', ] master_doc = 'index' link_files = { - '../CHANGES.rst': dict( - using=dict( - GH='https://github.com', - ), - replace=[ - dict( - pattern=r'(Issue #|\B#)(?P\d+)', - url='{package_url}/issues/{issue}', - ), - dict( - pattern=r'^(?m)((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n', - with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', - ), - dict( - pattern=r'PEP[- ](?P\d+)', - url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/', - ), - ], - ), + '../CHANGES.rst': dict( + using=dict( + GH='https://github.com', + ), + replace=[ + dict( + pattern=r'(Issue #|\B#)(?P\d+)', + url='{package_url}/issues/{issue}', + ), + dict( + pattern=r'^(?m)((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n', + with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', + ), + dict( + pattern=r'PEP[- ](?P\d+)', + url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/', + ), + ], + ), } diff --git a/pyproject.toml b/pyproject.toml index 65d74263..a8b44c14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,5 @@ [build-system] requires = ["setuptools>=34.4", "wheel", "setuptools_scm>=1.15"] + +[tool.black] +skip-string-normalization = true diff --git a/pytest.ini b/pytest.ini index 61dab3d4..15bb8b72 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] norecursedirs=dist build .tox .eggs -addopts=--doctest-modules --flake8 +addopts=--doctest-modules --flake8 --black doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= ignore:Possible nested set::pycodestyle:113 diff --git a/setup.cfg b/setup.cfg index adaed86d..78a0e465 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,7 @@ testing = pytest-sugar >= 0.9.1 collective.checkdocs pytest-flake8 + pytest-black # local diff --git a/setup.py b/setup.py index c990c529..3435b2ca 100644 --- a/setup.py +++ b/setup.py @@ -5,4 +5,4 @@ import setuptools if __name__ == '__main__': - setuptools.setup(use_scm_version=True) + setuptools.setup(use_scm_version=True) From 8a08fefa8561407bee150a7e6c0c9d5117ac5e7b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 12 Nov 2018 12:29:28 -0500 Subject: [PATCH 233/447] Remove workaround for pytest-sugar 159, now fixed. --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index 78cc7f9f..41e20a33 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,6 @@ minversion = 2.4 [testenv] deps = setuptools>=31.0.1 - pytest-sugar-bugfix159 commands = pytest {posargs} python setup.py checkdocs From 6de738440c6333e0f5e7b2447d2b5c05785481db Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 12 Nov 2018 12:31:25 -0500 Subject: [PATCH 234/447] Remove pytest-sugar plugin from standard pipelines as recommended in Frozenball/pytest-sugar#159. --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index adaed86d..2ea2224f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,6 @@ setup_requires = setuptools_scm >= 1.15.0 testing = # upstream pytest >= 3.5, !=3.7.3 - pytest-sugar >= 0.9.1 collective.checkdocs pytest-flake8 From 95af04d3fcf70a487f59c854d802d9bac193de53 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 28 Nov 2018 12:44:40 -0500 Subject: [PATCH 235/447] Prefer pytest-checkdocs to collective.checkdocs --- setup.cfg | 2 +- tox.ini | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 2ea2224f..30f3c087 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,7 @@ setup_requires = setuptools_scm >= 1.15.0 testing = # upstream pytest >= 3.5, !=3.7.3 - collective.checkdocs + pytest-checkdocs pytest-flake8 # local diff --git a/tox.ini b/tox.ini index 41e20a33..4121a91f 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,6 @@ deps = setuptools>=31.0.1 commands = pytest {posargs} - python setup.py checkdocs usedevelop = True extras = testing From 5c200dd4b98b91911ef9b7403373b84d017e42c0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 1 Dec 2018 10:46:24 -0500 Subject: [PATCH 236/447] Suppress deprecation warning in docutils --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest.ini b/pytest.ini index 61dab3d4..bbea8b12 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,3 +5,5 @@ doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= ignore:Possible nested set::pycodestyle:113 ignore:Using or importing the ABCs::flake8:410 + # workaround for https://sourceforge.net/p/docutils/bugs/348/ + ignore:'U' mode is deprecated::docutils.io From 2c91e8ec0d99f9ca354b7f913d61720925bb98bc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 1 Dec 2018 11:58:52 -0500 Subject: [PATCH 237/447] Remove use of setup_requires. Builders now require pip 10 or later to build/install from sdist. Older installers will still install the packages from wheels. Ref tox-dev/tox#809. --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 30f3c087..e0395d78 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,7 +24,6 @@ packages = find: include_package_data = true python_requires = >=2.7 install_requires = -setup_requires = setuptools_scm >= 1.15.0 [options.extras_require] testing = From 216c4336ddb5b498e429219ef765fa1ae857febd Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 11 Dec 2018 14:21:46 -0500 Subject: [PATCH 238/447] Revert "Remove use of setup_requires. Builders now require pip 10 or later to build/install from sdist. Older installers will still install the packages from wheels. Ref tox-dev/tox#809." This reverts commit 2c91e8ec0d99f9ca354b7f913d61720925bb98bc. --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index e0395d78..30f3c087 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ packages = find: include_package_data = true python_requires = >=2.7 install_requires = +setup_requires = setuptools_scm >= 1.15.0 [options.extras_require] testing = From 32b254dee01b5ef2b695ae04889af482c6cb28c3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 12 Dec 2018 13:26:23 -0500 Subject: [PATCH 239/447] Indicate build backend of setuptools --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 65d74263..efae667c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,3 @@ [build-system] requires = ["setuptools>=34.4", "wheel", "setuptools_scm>=1.15"] +build-backend = 'setuptools.build_meta' From a8bca166266fa2eeab931f6f20eef8e50048dddf Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 12 Dec 2018 17:08:36 -0500 Subject: [PATCH 240/447] Add support for cutting releases without DPL and using pep517. --- .travis.yml | 19 +++++++------------ install-pip-master.py | 21 +++++++++++++++++++++ setup.cfg | 3 --- tox.ini | 15 +++++++++++++++ 4 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 install-pip-master.py diff --git a/.travis.yml b/.travis.yml index b54e8e52..16363054 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,18 +13,13 @@ jobs: - stage: deploy if: tag IS present python: *latest_py3 - install: skip - script: skip - deploy: - provider: pypi - on: - tags: true - all_branches: true - user: jaraco - password: - secure: ... # encrypt password with `travis encrypt` - distributions: dists - skip_cleanup: true + before_script: skip + env: + - TWINE_USERNAME=jaraco + # TWINE_PASSWORD + - secure: ... # encrypt `TWINE_PASSWORD="{password}"` with `travis encrypt` + - TOX_TESTENV_PASSENV="TWINE_USERNAME TWINE_PASSWORD" + script: tox -e release cache: pip diff --git a/install-pip-master.py b/install-pip-master.py new file mode 100644 index 00000000..d62d20f3 --- /dev/null +++ b/install-pip-master.py @@ -0,0 +1,21 @@ +""" +In order to support installation of pep517 from source, +pip from master must be installed. +""" + +import subprocess +import sys + + +def main(): + cmd = [ + sys.executable, + '-m', 'pip', 'install', + 'git+https://github.com/pypa/pip', + ] + subprocess.run(cmd) + cmd[-1:] = sys.argv[1:] + subprocess.run(cmd) + + +__name__ == '__main__' and main() diff --git a/setup.cfg b/setup.cfg index 30f3c087..726b307e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[aliases] -dists = clean --all sdist bdist_wheel - [bdist_wheel] universal = 1 diff --git a/tox.ini b/tox.ini index 4121a91f..853d7def 100644 --- a/tox.ini +++ b/tox.ini @@ -17,3 +17,18 @@ extras = changedir = docs commands = python -m sphinx . {toxinidir}/build/html + +[testenv:release] +skip_install = True +# workaround for pep517 build support +install_command = python install-pip-master.py {opts} {packages} +deps = + # pull from feature branch for feature + git+https://github.com/pypa/pep517@feature/build-command + # workaround for https://github.com/pypa/twine/issues/423 + git+https://github.com/pypa/twine + path.py +commands = + python -c "import path; path.Path('dist').rmtree_p()" + python -m pep517.build . + python -m twine upload dist/* From bc8a6cdf948376e1c846a121a4e8e4a699c66909 Mon Sep 17 00:00:00 2001 From: Sebastian Kriems Date: Fri, 14 Dec 2018 16:19:36 +0100 Subject: [PATCH 241/447] spaces, style and formatters (#4) use spaces, fixed indentation, format using autopep8 --- docs/conf.py | 44 +++++++++++++++++++------------------------- setup.py | 4 ++-- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index aeda56c0..49a855ff 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,32 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -extensions = [ - 'sphinx.ext.autodoc', - 'jaraco.packaging.sphinx', - 'rst.linker', -] +extensions = ["sphinx.ext.autodoc", "jaraco.packaging.sphinx", "rst.linker"] -master_doc = 'index' +master_doc = "index" link_files = { - '../CHANGES.rst': dict( - using=dict( - GH='https://github.com', - ), - replace=[ - dict( - pattern=r'(Issue #|\B#)(?P\d+)', - url='{package_url}/issues/{issue}', - ), - dict( - pattern=r'^(?m)((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n', - with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', - ), - dict( - pattern=r'PEP[- ](?P\d+)', - url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/', - ), - ], - ), + "../CHANGES.rst": dict( + using=dict(GH="https://github.com"), + replace=[ + dict( + pattern=r"(Issue #|\B#)(?P\d+)", + url="{package_url}/issues/{issue}", + ), + dict( + pattern=r"^(?m)((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n", + with_scm="{text}\n{rev[timestamp]:%d %b %Y}\n", + ), + dict( + pattern=r"PEP[- ](?P\d+)", + url="https://www.python.org/dev/peps/pep-{pep_number:0>4}/", + ), + ], + ) } diff --git a/setup.py b/setup.py index c990c529..50e9f0c4 100644 --- a/setup.py +++ b/setup.py @@ -4,5 +4,5 @@ import setuptools -if __name__ == '__main__': - setuptools.setup(use_scm_version=True) +if __name__ == "__main__": + setuptools.setup(use_scm_version=True) From 939c515f2cc01525cbbd71f26e71d21471abdc93 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 17 Dec 2018 12:17:02 -0500 Subject: [PATCH 242/447] Rely on pep517 0.5 --- install-pip-master.py | 21 --------------------- tox.ini | 5 +---- 2 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 install-pip-master.py diff --git a/install-pip-master.py b/install-pip-master.py deleted file mode 100644 index d62d20f3..00000000 --- a/install-pip-master.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -In order to support installation of pep517 from source, -pip from master must be installed. -""" - -import subprocess -import sys - - -def main(): - cmd = [ - sys.executable, - '-m', 'pip', 'install', - 'git+https://github.com/pypa/pip', - ] - subprocess.run(cmd) - cmd[-1:] = sys.argv[1:] - subprocess.run(cmd) - - -__name__ == '__main__' and main() diff --git a/tox.ini b/tox.ini index 853d7def..70b0be7a 100644 --- a/tox.ini +++ b/tox.ini @@ -20,11 +20,8 @@ commands = [testenv:release] skip_install = True -# workaround for pep517 build support -install_command = python install-pip-master.py {opts} {packages} deps = - # pull from feature branch for feature - git+https://github.com/pypa/pep517@feature/build-command + pep517>=0.5 # workaround for https://github.com/pypa/twine/issues/423 git+https://github.com/pypa/twine path.py From 192dafa3e9943e971a004d404be1b8e0d20691f2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 20 Dec 2018 18:11:27 -0500 Subject: [PATCH 243/447] Add documentation on the skeleton. Fixes #5. --- .travis.yml | 2 +- setup.py | 2 - skeleton.md | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 skeleton.md diff --git a/.travis.yml b/.travis.yml index 16363054..8fc89320 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ jobs: env: - TWINE_USERNAME=jaraco # TWINE_PASSWORD - - secure: ... # encrypt `TWINE_PASSWORD="{password}"` with `travis encrypt` + - secure: ... - TOX_TESTENV_PASSENV="TWINE_USERNAME TWINE_PASSWORD" script: tox -e release diff --git a/setup.py b/setup.py index 50e9f0c4..827e955f 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -# Project skeleton maintained at https://github.com/jaraco/skeleton - import setuptools if __name__ == "__main__": diff --git a/skeleton.md b/skeleton.md new file mode 100644 index 00000000..bc78f37c --- /dev/null +++ b/skeleton.md @@ -0,0 +1,126 @@ +# Overview + +This project is merged with [skeleton](https://github.com/jaraco/skeleton). What is skeleton? It's the scaffolding of a Python project jaraco [introduced in his blog](https://blog.jaraco.com/a-project-skeleton-for-python-projects/). It seeks to provide a means to re-use techniques and inherit advances when managing projects for distribution. + +## An SCM Managed Approach + +While maintaining dozens of projects in PyPI, jaraco derives best practices for project distribution and publishes them in the [skeleton repo](https://github.com/jaraco/skeleton), a git repo capturing the evolution and culmination of these best practices. + +It's intended to be used by a new or existing project to adopt these practices and honed and proven techniques. Adopters are encouraged to use the project directly and maintain a small deviation from the technique, make their own fork for more substantial changes unique to their environment or preferences, or simply adopt the skeleton once and abandon it thereafter. + +The primary advantage to using an SCM for maintaining these techniques is that those tools help facilitate the merge between the template and its adopting projects. + +# Usage + +## new projects + +To use skeleton for a new project, simply pull the skeleton into a new project: + +``` +$ git init my-new-project +$ cd my-new-project +$ git pull gh://jaraco/skeleton +``` + +Now customize the project to suit your individual project needs. + +## existing projects + +If you have an existing project, you can still incorporate the skeleton by merging it into the codebase. + +``` +$ git merge skeleton --allow-unrelated-histories +``` + +The `--allow-unrelated-histories` is necessary because the history from the skeleton was previously unrelated to the existing codebase. Resolve any merge conflicts and commit to the master, and now the project is based on the shared skeleton. + +## Updating + +Whenever a change is needed or desired for the general technique for packaging, it can be made in the skeleton project and then merged into each of the derived projects as needed, recommended before each release. As a result, features and best practices for packaging are centrally maintained and readily trickle into a whole suite of packages. This technique lowers the amount of tedious work necessary to create or maintain a project, and coupled with other techniques like continuous integration and deployment, lowers the cost of creating and maintaining refined Python projects to just a few, familiar git operations. + +Thereafter, the target project can make whatever customizations it deems relevant to the scaffolding. The project may even at some point decide that the divergence is too great to merit renewed merging with the original skeleton. This approach applies maximal guidance while creating minimal constraints. + +# Features + +The features/techniques employed by the skeleton include: + +- PEP 517/518 based build relying on setuptools as the build tool +- setuptools declarative configuration using setup.cfg +- tox for running tests +- A README.rst as reStructuredText with some popular badges, but with readthedocs and appveyor badges commented out +- A CHANGES.rst file intended for publishing release notes about the project. + +## Packaging Conventions + +A pyproject.toml is included to enable PEP 517 and PEP 518 compatibility and declares the requirements necessary to build the project on setuptools (a minimum version compatible with setup.cfg declarative config). + +The setup.cfg file implements the following features: + +- Assumes universal wheel for release +- Advertises the project's LICENSE file (MIT by default) +- Reads the README.rst file into the long description +- Some common Trove classifiers +- Includes all packages discovered in the repo +- Data files in the package are also included (not just Python files) +- Declares the required Python versions +- Declares install requirements (empty by default) +- Declares setup requirements for legacy environments +- Supplies two 'extras': + - testing: requirements for running tests + - docs: requirements for building docs + - these extras split the declaration into "upstream" (requirements as declared by the skeleton) and "local" (those specific to the local project) +- Placeholder for defining entry points + +Additionally, the setup.py file declares `use_scm_version` which relies on [setuptools_scm](https://pypi.org/project/setuptools_scm) to do two things: + +- derive the project version from SCM tags +- ensure that all files committed to the repo are automatically included in releases + +## Running Tests + +The skeleton assumes the developer has [tox](https://pypi.org/project/tox) installed. The developer is expected to run `tox` to run tests on the current Python version using [pytest](https://pypi.org/project/pytest). + +Other environments (invoked with `tox -e {name}`) supplied include: + + - a `build-docs` environment to build the documentation + - a `release` environment to publish the package to PyPI + +A pytest.ini is included to define common options around running tests. In particular: + +- rely on default test discovery in the current directory +- avoid recursing into common directories not containing tests +- run doctests on modules and invoke flake8 tests +- in doctests, allow unicode literals and regular literals to match, allowing for doctests to run on Python 2 and 3. Also enable ELLIPSES, a default that would be undone by supplying the prior option. +- filters out known warnings caused by libraries/functionality included by the skeleton + +Relies a .flake8 file to correct some default behaviors: + +- allow tabs for indentation (legacy for jaraco projects) +- disable mutually incompatible rules W503 and W504. + +## Continuous Integration + +The project is pre-configured to run tests in [Travis-CI](https://travis-ci.org) (.travis.yml). Any new project must be enabled either through their web site or with the `travis enable` command. In addition to running tests, an additional deploy stage is configured to automatically release tagged commits. The username and password for PyPI must be configured for each project using the `travis` command and only after the travis project is created. As releases are cut with [twine](https://pypi.org/project/twine), the two values are supplied through the `TWINE_USERNAME` and `TWINE_PASSWORD`. To configure the latter as a secret, run the following command: + +``` +echo "TWINE_PASSWORD={password}" | travis encrypt +``` + +Or disable it in the CI definition and configure it through the web UI. + +Features include: +- test against Python 2 and 3 +- run on Ubuntu Xenial +- correct for broken IPv6 + +Also provided is a minimal template for running under Appveyor (Windows). + +## Building Documentation + +Documentation is automatically built by [Read the Docs](https://readthedocs.org) when the project is registered with it, by way of the .readthedocs.yml file. To test the docs build manually, a tox env may be invoked as `tox -e build-docs`. Both techniques rely on the dependencies declared in `setup.cfg/options.extras_require.docs`. + +In addition to building the sphinx docs scaffolded in `docs/`, the docs build a `history.html` file that first injects release dates and hyperlinks into the CHANGES.rst before incorporating it as history in the docs. + +## Cutting releases + +By default, tagged commits are released through the continuous integration deploy stage. From 5b4c2503ce84744c0cdf398316d6b18863905297 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 27 Dec 2018 11:42:55 -0500 Subject: [PATCH 244/447] Add workaround for DeprecationWarning in flake8 --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest.ini b/pytest.ini index bbea8b12..9b3c1ecd 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,3 +7,5 @@ filterwarnings= ignore:Using or importing the ABCs::flake8:410 # workaround for https://sourceforge.net/p/docutils/bugs/348/ ignore:'U' mode is deprecated::docutils.io + # workaround for https://gitlab.com/pycqa/flake8/issues/275 + ignore:You passed a bytestring as `filenames`.::flake8 From 8ac0f8736c746a829e6393ca5ba00fa8d042d426 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 28 Dec 2018 21:57:02 -0500 Subject: [PATCH 245/447] Use consistent encoding quoting in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index efae667c..6f0a5168 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [build-system] requires = ["setuptools>=34.4", "wheel", "setuptools_scm>=1.15"] -build-backend = 'setuptools.build_meta' +build-backend = "setuptools.build_meta" From 4310c976400dc2eab8d8597b0dffaa7b787cff71 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 28 Dec 2018 22:00:08 -0500 Subject: [PATCH 246/447] Clarify purpose of local/upstream extras --- skeleton.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skeleton.md b/skeleton.md index bc78f37c..0e0c0eff 100644 --- a/skeleton.md +++ b/skeleton.md @@ -68,7 +68,7 @@ The setup.cfg file implements the following features: - Supplies two 'extras': - testing: requirements for running tests - docs: requirements for building docs - - these extras split the declaration into "upstream" (requirements as declared by the skeleton) and "local" (those specific to the local project) + - these extras split the declaration into "upstream" (requirements as declared by the skeleton) and "local" (those specific to the local project); these markers help avoid merge conflicts - Placeholder for defining entry points Additionally, the setup.py file declares `use_scm_version` which relies on [setuptools_scm](https://pypi.org/project/setuptools_scm) to do two things: From 6067a2228426cf28f832d1df852fce90f0d4656d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 4 Jan 2019 21:47:17 -0500 Subject: [PATCH 247/447] Move modules --- calendra/{ => america}/canada.py | 0 calendra/{oceania.py => oceania/australia.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename calendra/{ => america}/canada.py (100%) rename calendra/{oceania.py => oceania/australia.py} (100%) diff --git a/calendra/canada.py b/calendra/america/canada.py similarity index 100% rename from calendra/canada.py rename to calendra/america/canada.py diff --git a/calendra/oceania.py b/calendra/oceania/australia.py similarity index 100% rename from calendra/oceania.py rename to calendra/oceania/australia.py From b84ce5b0786ac45ba0ffe0a6500c51410d413647 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 4 Jan 2019 22:21:46 -0500 Subject: [PATCH 248/447] Feed the hobgoblins (delint). --- calendra/africa/south_africa.py | 4 ++-- calendra/tests/test_asia.py | 1 + calendra/tests/test_registry_europe.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/calendra/africa/south_africa.py b/calendra/africa/south_africa.py index 4997f510..3e030ddd 100644 --- a/calendra/africa/south_africa.py +++ b/calendra/africa/south_africa.py @@ -2,10 +2,10 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -from datetime import timedelta, date +from datetime import date from ..core import WesternCalendar -from ..core import SUN, MON, FRI +from ..core import MON, FRI from ..core import ChristianMixin from ..exceptions import CalendarError from ..registry import iso_register diff --git a/calendra/tests/test_asia.py b/calendra/tests/test_asia.py index aa392e36..de4c1236 100644 --- a/calendra/tests/test_asia.py +++ b/calendra/tests/test_asia.py @@ -5,6 +5,7 @@ from ..asia import HongKong, Japan, Qatar, Singapore from ..asia import SouthKorea, Taiwan, Malaysia + class HongKongTest(GenericCalendarTest): cal_class = HongKong diff --git a/calendra/tests/test_registry_europe.py b/calendra/tests/test_registry_europe.py index b4347c0a..87743c66 100644 --- a/calendra/tests/test_registry_europe.py +++ b/calendra/tests/test_registry_europe.py @@ -43,7 +43,7 @@ def test_europe(self): self.assertIn(Estonia, classes) self.assertIn(Denmark, classes) self.assertIn(Finland, classes) - self.assertIn(France, classes) + self.assertIn(France, classes) self.assertIn(Greece, classes) self.assertIn(Hungary, classes) self.assertIn(Iceland, classes) @@ -61,7 +61,7 @@ def test_europe(self): self.assertIn(Russia, classes) self.assertIn(Slovakia, classes) self.assertIn(Slovenia, classes) - self.assertIn(Spain, classes) + self.assertIn(Spain, classes) self.assertIn(Sweden, classes) self.assertIn(Switzerland, classes) self.assertIn(Vaud, classes) From 6f91112582978d9435ea65d6fe668f010760e026 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 4 Jan 2019 22:28:06 -0500 Subject: [PATCH 249/447] Fix failing doctests --- calendra/core.py | 1 + calendra/registry.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/calendra/core.py b/calendra/core.py index a0575c95..0b6af53e 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -461,6 +461,7 @@ def get_working_days_delta(self, start, end): In France, April 1st 2018 is a holiday because it's Easter monday: + >>> from calendra.europe import France >>> cal = France() >>> cal.get_working_days_delta(day1, day2) 4 diff --git a/calendra/registry.py b/calendra/registry.py index 0ff772b2..5741ebc0 100644 --- a/calendra/registry.py +++ b/calendra/registry.py @@ -52,7 +52,7 @@ def get_subregions(self, iso_code): >>> registry = IsoRegistry() >>> # assuming calendars registered are: DE, DE-HH, DE-BE - >>> registry.get_subregions('DE') + >>> registry.get_subregions('DE') # doctest: +SKIP {'DE-HH': , 'DE-BE': } :rtype dict @@ -99,8 +99,8 @@ def iso_register(iso_code): >>> from calendra.core import Calendar >>> @iso_register('MC-MR') - >>> class MyRegion(Calendar): - >>> 'My Region' + ... class MyRegion(Calendar): + ... 'My Region' Region calendar is then retrievable from registry: From 12eed1326e1bc26ce256e7b3f8cd8d3a5beab2d5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 6 Feb 2019 09:54:00 -0500 Subject: [PATCH 250/447] Suppress E117 as workaround for PyCQA/pycodestyle#836 --- .flake8 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.flake8 b/.flake8 index c85d34a7..04d2d97a 100644 --- a/.flake8 +++ b/.flake8 @@ -2,6 +2,8 @@ ignore = # Allow tabs for indentation W191 + # Workaround for https://github.com/PyCQA/pycodestyle/issues/836 + E117 # W503 violates spec https://github.com/PyCQA/pycodestyle/issues/513 W503 # W504 has issues https://github.com/OCA/maintainer-quality-tools/issues/545 From 8186f76e906f80d678e895f6627afefee5617888 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 6 Feb 2019 09:58:28 -0500 Subject: [PATCH 251/447] Amend skeleton documentation to expand on the value of the approach. --- skeleton.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skeleton.md b/skeleton.md index 0e0c0eff..09485cca 100644 --- a/skeleton.md +++ b/skeleton.md @@ -10,6 +10,8 @@ It's intended to be used by a new or existing project to adopt these practices a The primary advantage to using an SCM for maintaining these techniques is that those tools help facilitate the merge between the template and its adopting projects. +Another advantage to using an SCM-managed approach is that tools like GitHub recognize that a change in the skeleton is the _same change_ across all projects that merge with that skeleton. Without the ancestry, with a traditional copy/paste approach, a [commit like this](https://github.com/jaraco/skeleton/commit/12eed1326e1bc26ce256e7b3f8cd8d3a5beab2d5) would produce notifications in the upstream project issue for each and every application, but because it's centralized, GitHub provides just the one notification when the change is added to the skeleton. + # Usage ## new projects From cdff6c8f0fa2a0439adc219a40bc2b11bb95d29d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 22 Mar 2019 11:49:13 -0400 Subject: [PATCH 252/447] Remove sudo declaration in Travis config. See https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration for more details. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8fc89320..17d02624 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ dist: xenial -sudo: false language: python python: From bb4c6091319fc3d33d4aebd15da483bb90acdbc9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 17 Apr 2019 15:22:26 -0400 Subject: [PATCH 253/447] Enable tox-pip-extensions ext_venv_update if available. Fixes jaraco/skeleton#6 --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 70b0be7a..5ce2047b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,8 @@ [tox] envlist = python minversion = 2.4 +# https://github.com/jaraco/skeleton/issues/6 +tox_pip_extensions_ext_venv_update = true [testenv] deps = From 5bd3e6069e32cc94725fa389758e055126f3cdc5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 24 Apr 2019 09:18:44 -0400 Subject: [PATCH 254/447] Rely on tox 3.2 and pip 10 or later for all builds --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5ce2047b..78161a56 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,8 @@ [tox] envlist = python -minversion = 2.4 +minversion = 3.2 +requires = + pip >= 10 # https://github.com/jaraco/skeleton/issues/6 tox_pip_extensions_ext_venv_update = true From 51f138939c98a4f616c702bc2f080504395fbbd6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 24 Apr 2019 11:17:47 -0400 Subject: [PATCH 255/447] It adds no value to add a pip requirement for the tox install --- tox.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tox.ini b/tox.ini index 78161a56..8a3bf67c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,6 @@ [tox] envlist = python minversion = 3.2 -requires = - pip >= 10 # https://github.com/jaraco/skeleton/issues/6 tox_pip_extensions_ext_venv_update = true From 123b0b20d6e0bc9ffd00d7fb8c2e1a3ceee7475a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 24 Apr 2019 13:20:53 -0400 Subject: [PATCH 256/447] Pin to pip 19.0 for now for pypa/pip#6434. --- pin-pip.py | 20 ++++++++++++++++++++ tox.ini | 1 + 2 files changed, 21 insertions(+) create mode 100644 pin-pip.py diff --git a/pin-pip.py b/pin-pip.py new file mode 100644 index 00000000..4cf0383c --- /dev/null +++ b/pin-pip.py @@ -0,0 +1,20 @@ +""" +Downgrade to pip 19.0 before installing requirements, working +around limitations introduced in 19.1 (ref +https://github.com/pypa/pip/issues/6434) +""" + +import sys +import subprocess +import shlex + + +def main(): + subprocess.check_call(shlex.split( + 'python -m pip install pip<19.1' + )) + subprocess.check_call(shlex.split( + 'python -m pip install') + sys.argv[1:]) + + +__name__ == '__main__' and main() diff --git a/tox.ini b/tox.ini index 8a3bf67c..3d1ae59d 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ minversion = 3.2 tox_pip_extensions_ext_venv_update = true [testenv] +install_command = python pin-pip.py {opts} {packages} deps = setuptools>=31.0.1 commands = From 4186b77c3f225a5845ac9072b167427c91f1d6fd Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 9 May 2019 08:55:31 -0400 Subject: [PATCH 257/447] Revert "Pin to pip 19.0 for now for pypa/pip#6434." This reverts commit 123b0b20d6e0bc9ffd00d7fb8c2e1a3ceee7475a. --- pin-pip.py | 20 -------------------- tox.ini | 1 - 2 files changed, 21 deletions(-) delete mode 100644 pin-pip.py diff --git a/pin-pip.py b/pin-pip.py deleted file mode 100644 index 4cf0383c..00000000 --- a/pin-pip.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -Downgrade to pip 19.0 before installing requirements, working -around limitations introduced in 19.1 (ref -https://github.com/pypa/pip/issues/6434) -""" - -import sys -import subprocess -import shlex - - -def main(): - subprocess.check_call(shlex.split( - 'python -m pip install pip<19.1' - )) - subprocess.check_call(shlex.split( - 'python -m pip install') + sys.argv[1:]) - - -__name__ == '__main__' and main() diff --git a/tox.ini b/tox.ini index 3d1ae59d..8a3bf67c 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,6 @@ minversion = 3.2 tox_pip_extensions_ext_venv_update = true [testenv] -install_command = python pin-pip.py {opts} {packages} deps = setuptools>=31.0.1 commands = From 4399038f2eab21f942a5462e0f5b1351e6203873 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 15 May 2019 19:45:15 -0400 Subject: [PATCH 258/447] Only install and invoke pytest-black on Python 3 --- pytest.ini | 2 +- setup.cfg | 2 +- tox.ini | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pytest.ini b/pytest.ini index 10681adf..9b3c1ecd 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] norecursedirs=dist build .tox .eggs -addopts=--doctest-modules --flake8 --black +addopts=--doctest-modules --flake8 doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= ignore:Possible nested set::pycodestyle:113 diff --git a/setup.cfg b/setup.cfg index a3eb3c94..235303f0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,7 +29,7 @@ testing = pytest >= 3.5, !=3.7.3 pytest-checkdocs pytest-flake8 - pytest-black + pytest-black; python_version >= "3" # local diff --git a/tox.ini b/tox.ini index 8a3bf67c..8fa79660 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,8 @@ tox_pip_extensions_ext_venv_update = true deps = setuptools>=31.0.1 commands = - pytest {posargs} + !py27: pytest --black {posargs} + py27: pytest {posargs} usedevelop = True extras = testing From d4c65e6784e783549bfe5bba1ccbc7be76eb41ff Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 15 May 2019 21:21:57 -0400 Subject: [PATCH 259/447] Use pytest-black-multipy to enable simple support for pytest-black where available. Ref pytest-dev/pytest#5272. --- pytest.ini | 2 +- setup.cfg | 2 +- tox.ini | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pytest.ini b/pytest.ini index 9b3c1ecd..10681adf 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] norecursedirs=dist build .tox .eggs -addopts=--doctest-modules --flake8 +addopts=--doctest-modules --flake8 --black doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= ignore:Possible nested set::pycodestyle:113 diff --git a/setup.cfg b/setup.cfg index 235303f0..9345869b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,7 +29,7 @@ testing = pytest >= 3.5, !=3.7.3 pytest-checkdocs pytest-flake8 - pytest-black; python_version >= "3" + pytest-black-multipy # local diff --git a/tox.ini b/tox.ini index 8fa79660..8a3bf67c 100644 --- a/tox.ini +++ b/tox.ini @@ -8,8 +8,7 @@ tox_pip_extensions_ext_venv_update = true deps = setuptools>=31.0.1 commands = - !py27: pytest --black {posargs} - py27: pytest {posargs} + pytest {posargs} usedevelop = True extras = testing From 79733f08c43f9b2e0fd1830b37311fa52a16537c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 16 May 2019 10:21:15 -0400 Subject: [PATCH 260/447] Update skeleton documentation to reflect black adoption. --- skeleton.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/skeleton.md b/skeleton.md index 09485cca..72494070 100644 --- a/skeleton.md +++ b/skeleton.md @@ -50,7 +50,8 @@ The features/techniques employed by the skeleton include: - setuptools declarative configuration using setup.cfg - tox for running tests - A README.rst as reStructuredText with some popular badges, but with readthedocs and appveyor badges commented out -- A CHANGES.rst file intended for publishing release notes about the project. +- A CHANGES.rst file intended for publishing release notes about the project +- Use of [black](https://black.readthedocs.io/en/stable/) for code formatting (disabled on unsupported Python 3.5 and earlier) ## Packaging Conventions @@ -97,8 +98,8 @@ A pytest.ini is included to define common options around running tests. In parti Relies a .flake8 file to correct some default behaviors: -- allow tabs for indentation (legacy for jaraco projects) -- disable mutually incompatible rules W503 and W504. +- disable mutually incompatible rules W503 and W504 +- support for black format ## Continuous Integration From 2ab127d2bc47ffd747afe3059b3a5b08254a5415 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 23 May 2019 08:09:02 -0400 Subject: [PATCH 261/447] Rely on twine 1.13 or later --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 8a3bf67c..5f1d1b7f 100644 --- a/tox.ini +++ b/tox.ini @@ -24,8 +24,7 @@ commands = skip_install = True deps = pep517>=0.5 - # workaround for https://github.com/pypa/twine/issues/423 - git+https://github.com/pypa/twine + twine>=1.13 path.py commands = python -c "import path; path.Path('dist').rmtree_p()" From f36c7daeca24357c808270156b23cf4f46c138fa Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 30 Jun 2019 03:39:06 -0400 Subject: [PATCH 262/447] Update docs to redirect to Workalendar's new markdown changelog --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index c9f06ada..aa292ef8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,7 +30,7 @@ ), dict( pattern=r'(?P[Ww]orkalendar \d+\.\d+(\.\d+)?)', - url='{workalendar}blob/master/CHANGELOG', + url='{workalendar}blob/master/Changelog.md', ), ], ) From 9b6fc306f15f13b719e3187299ccf534679676b0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 30 Jun 2019 03:55:48 -0400 Subject: [PATCH 263/447] =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins?= =?UTF-8?q?=20(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- calendra/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calendra/core.py b/calendra/core.py index 0b6af53e..ed59d982 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -877,7 +877,7 @@ def get_variable_days(self, year): conversion_method = getattr( self.calverter, '%s_to_jd' % self.conversion_method) for month, day, label in self.get_islamic_holidays(): - for y in years: + for y in years: # noqa: E117 jd = conversion_method(y, month, day) g_year, g_month, g_day = self.calverter.jd_to_gregorian(jd) if g_year == year: From 054ea7dbbaabf257e7c3c6276d889cc178a19340 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 30 Jun 2019 16:38:07 -0400 Subject: [PATCH 264/447] Upgrade tox and virtualenv to ensure that environments get recent pips --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 2b7808f9..f35aa27d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,7 +18,7 @@ cache: - '%LOCALAPPDATA%\pip\Cache' test_script: - - "python -m pip install tox tox-venv" + - "python -m pip install -U tox tox-venv virtualenv" - "tox" version: '{build}' From 64e22dbd1b099058e5e9d30425e3b105376d234a Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Thu, 11 Jul 2019 09:06:19 +0100 Subject: [PATCH 265/447] Get pytest running. --- calendra/tests/test_global_registry.py | 2 +- calendra/tests/test_registry.py | 2 +- calendra/tests/test_registry_africa.py | 2 +- calendra/tests/test_registry_america.py | 2 +- calendra/tests/test_registry_asia.py | 2 +- calendra/tests/test_registry_europe.py | 2 +- calendra/tests/test_registry_oceania.py | 2 +- calendra/tests/test_registry_usa.py | 2 +- setup.py | 7 ++++++- 9 files changed, 14 insertions(+), 9 deletions(-) diff --git a/calendra/tests/test_global_registry.py b/calendra/tests/test_global_registry.py index fdcbb821..01bc88ca 100644 --- a/calendra/tests/test_global_registry.py +++ b/calendra/tests/test_global_registry.py @@ -1,6 +1,6 @@ from unittest import TestCase -from ..registry import registry +from ..registry_tools import registry class GlobalRegistry(TestCase): diff --git a/calendra/tests/test_registry.py b/calendra/tests/test_registry.py index a1493977..20cddddf 100644 --- a/calendra/tests/test_registry.py +++ b/calendra/tests/test_registry.py @@ -1,7 +1,7 @@ from datetime import date from unittest import TestCase -from ..registry import IsoRegistry +from ..registry_tools import IsoRegistry from ..core import Calendar diff --git a/calendra/tests/test_registry_africa.py b/calendra/tests/test_registry_africa.py index 56e13556..111d4abb 100644 --- a/calendra/tests/test_registry_africa.py +++ b/calendra/tests/test_registry_africa.py @@ -10,7 +10,7 @@ SouthAfrica, ) -from ..registry import registry +from ..registry_tools import registry class RegistryAfrica(TestCase): diff --git a/calendra/tests/test_registry_america.py b/calendra/tests/test_registry_america.py index aad498ab..f1efd396 100644 --- a/calendra/tests/test_registry_america.py +++ b/calendra/tests/test_registry_america.py @@ -29,7 +29,7 @@ ) from ..america import Chile, Colombia, Mexico, Panama, Paraguay -from ..registry import registry +from ..registry_tools import registry class RegistryAmerica(TestCase): diff --git a/calendra/tests/test_registry_asia.py b/calendra/tests/test_registry_asia.py index e6cbde83..0b24b574 100644 --- a/calendra/tests/test_registry_asia.py +++ b/calendra/tests/test_registry_asia.py @@ -12,7 +12,7 @@ Taiwan, ) -from ..registry import registry +from ..registry_tools import registry class RegistryAsia(TestCase): diff --git a/calendra/tests/test_registry_europe.py b/calendra/tests/test_registry_europe.py index 87743c66..1f0f86f9 100644 --- a/calendra/tests/test_registry_europe.py +++ b/calendra/tests/test_registry_europe.py @@ -19,7 +19,7 @@ SaxonyAnhalt, SchleswigHolstein, Thuringia ) -from ..registry import registry +from ..registry_tools import registry classes = (v for k, v in registry.region_registry.items()) classes = list(classes) diff --git a/calendra/tests/test_registry_oceania.py b/calendra/tests/test_registry_oceania.py index 8c20e3e8..c681c947 100644 --- a/calendra/tests/test_registry_oceania.py +++ b/calendra/tests/test_registry_oceania.py @@ -18,7 +18,7 @@ WesternAustralia ) -from ..registry import registry +from ..registry_tools import registry AUSTRALIAN_TERRITORIES = ( AustralianCapitalTerritory, diff --git a/calendra/tests/test_registry_usa.py b/calendra/tests/test_registry_usa.py index c4831738..30698ca5 100644 --- a/calendra/tests/test_registry_usa.py +++ b/calendra/tests/test_registry_usa.py @@ -55,7 +55,7 @@ Wyoming ) -from ..registry import registry +from ..registry_tools import registry class RegistryUsa(TestCase): diff --git a/setup.py b/setup.py index 493b4daf..e99dc608 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,12 @@ setuptools.setup( requires=[ 'pyluach', - ] + ], + # install_requires=[ + # 'pandas', + # 'pytest-cov', + # 'pytest-flake8', + # ], ) if __name__ == "__main__": From 08818e493f0527c371421a995b4dd302af533cc5 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Thu, 11 Jul 2019 09:58:41 +0100 Subject: [PATCH 266/447] Use same spelling as upstream (even though it is inconsistent). --- calendra/europe/united_kingdom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calendra/europe/united_kingdom.py b/calendra/europe/united_kingdom.py index 04643bbe..8989ebce 100644 --- a/calendra/europe/united_kingdom.py +++ b/calendra/europe/united_kingdom.py @@ -36,7 +36,7 @@ def get_early_may_bank_holiday(self, year): if year == 2020: return Holiday( date(year, 5, 8), - "Early May Bank Holiday (VE day)", + "Early May bank holiday (VE day)", indication="VE day", ) else: From 9ade35a900abc7002caedbf484913fec0ae50300 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Thu, 11 Jul 2019 09:59:26 +0100 Subject: [PATCH 267/447] Linter issues--; --- calendra/europe/united_kingdom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/calendra/europe/united_kingdom.py b/calendra/europe/united_kingdom.py index 8989ebce..a3ae9d24 100644 --- a/calendra/europe/united_kingdom.py +++ b/calendra/europe/united_kingdom.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals from datetime import date -from dateutil import relativedelta as rd from ..core import WesternCalendar, ChristianMixin from ..core import Holiday @@ -54,7 +53,8 @@ def get_spring_bank_holiday(self, year): elif year == 2002: spring_bank_holiday = date(2002, 6, 4) else: - spring_bank_holiday = UnitedKingdom.get_last_weekday_in_month(year, 5, MON) + spring_bank_holiday = UnitedKingdom. \ + get_last_weekday_in_month(year, 5, MON) return Holiday( spring_bank_holiday, "Spring Bank Holiday", From ca90ebb2677f63083625de163ff0c9e3f7fa9dc4 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Thu, 11 Jul 2019 09:59:46 +0100 Subject: [PATCH 268/447] Adapt tests to reflect separation of observed dates. --- calendra/tests/test_europe.py | 4 ++-- calendra/tests/test_turkey.py | 2 +- calendra/tests/test_usa.py | 20 +++++--------------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/calendra/tests/test_europe.py b/calendra/tests/test_europe.py index ed66c308..a33ea77b 100644 --- a/calendra/tests/test_europe.py +++ b/calendra/tests/test_europe.py @@ -143,7 +143,7 @@ def test_holidays_2016(self): def test_holidays_2017(self): holidays = self.cal.holidays_set(2017) self.assertIn(date(2017, 1, 1), holidays) - self.assertIn(date(2017, 1, 2), holidays) # New Year boxing + self.assertNotIn(date(2017, 1, 2), holidays) # New Year boxing self.assertIn(date(2017, 1, 23), holidays) # National Heroes Day self.assertIn(date(2017, 3, 1), holidays) # Ash Wednesday self.assertIn(date(2017, 4, 14), holidays) # good friday @@ -870,7 +870,7 @@ def test_year_2011(self): def test_year_2012(self): holidays = self.cal.holidays_set(2012) self.assertIn(date(2012, 1, 1), holidays) # new year day - self.assertIn(date(2012, 1, 2), holidays) # new year shift + self.assertNotIn(date(2012, 1, 2), holidays) # new year shift self.assertIn(date(2012, 4, 6), holidays) # good friday self.assertIn(date(2012, 4, 8), holidays) # easter sunday self.assertIn(date(2012, 4, 9), holidays) # easter monday diff --git a/calendra/tests/test_turkey.py b/calendra/tests/test_turkey.py index 13363de8..43c25cf2 100644 --- a/calendra/tests/test_turkey.py +++ b/calendra/tests/test_turkey.py @@ -11,7 +11,7 @@ class TurkeyTest(GenericCalendarTest): def test_year_new_year_shift(self): holidays = self.cal.holidays_set(2012) self.assertIn(date(2012, 1, 1), holidays) - self.assertIn(date(2012, 1, 2), holidays) + self.assertNotIn(date(2012, 1, 2), holidays) holidays = self.cal.holidays_set(2013) self.assertIn(date(2013, 1, 1), holidays) self.assertNotIn(date(2013, 1, 2), holidays) diff --git a/calendra/tests/test_usa.py b/calendra/tests/test_usa.py index c49ee04d..595772e1 100644 --- a/calendra/tests/test_usa.py +++ b/calendra/tests/test_usa.py @@ -1740,8 +1740,7 @@ def test_shift_2015(self): observed = date(2015, 7, 3) self.assertIn(fourth_july, holiday_dict) self.assertEqual(holiday_dict[fourth_july], "Independence Day") - self.assertIn(observed, holiday_dict) - self.assertEqual(holiday_dict[observed], "Independence Day (Observed)") + self.assertNotIn(observed, holiday_dict) def test_shift_2010(self): # Test a normal shift on 4th of July. @@ -1752,19 +1751,14 @@ def test_shift_2010(self): observed = date(2010, 7, 5) self.assertIn(fourth_july, holiday_dict) self.assertEqual(holiday_dict[fourth_july], "Independence Day") - self.assertIn(observed, holiday_dict) - self.assertEqual(holiday_dict[observed], "Independence Day (Observed)") + self.assertNotIn(observed, holiday_dict) def test_new_years_shift(self): # If January, 1st *of the year after* happens on SAT, add New Years Eve holidays = self.cal.holidays(2010) holiday_dict = dict(holidays) new_years_eve = date(2010, 12, 31) - self.assertIn(new_years_eve, holiday_dict) - self.assertEqual( - holiday_dict[new_years_eve], - "New Years Day (Observed)" - ) + self.assertNotIn(new_years_eve, holiday_dict) # The year after, it's not shifted holidays = self.cal.holidays_set(2011) new_years_eve = date(2011, 12, 31) @@ -1778,12 +1772,10 @@ def test_christmas_extra_shift_2010(self): # * 23rd (XMas Eve shifted on THU) holidays = self.cal.holidays(2010) holiday_dict = dict(holidays) - dec_23rd = date(2010, 12, 23) dec_24th = date(2010, 12, 24) dec_25th = date(2010, 12, 25) - for day in (dec_23rd, dec_24th, dec_25th): + for day in (dec_24th, dec_25th): self.assertIn(day, holiday_dict) - self.assertEqual(holiday_dict[dec_23rd], "Christmas Eve (Observed)") self.assertEqual(holiday_dict[dec_24th], "Christmas Eve") self.assertEqual(holiday_dict[dec_25th], "Christmas Day") @@ -1796,12 +1788,10 @@ def test_christmas_extra_shift_2006(self): holiday_dict = dict(holidays) dec_24th = date(2006, 12, 24) dec_25th = date(2006, 12, 25) - dec_26th = date(2006, 12, 26) - for day in (dec_24th, dec_25th, dec_26th): + for day in (dec_24th, dec_25th): self.assertIn(day, holiday_dict) self.assertEqual(holiday_dict[dec_24th], "Christmas Eve") self.assertEqual(holiday_dict[dec_25th], "Christmas Day") - self.assertEqual(holiday_dict[dec_26th], "Christmas Day (Observed)") class NormalShiftTestCaseExceptions(UnitedStatesTest): From 3b9092f21ad7485044177378051f1fe71c006e03 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Thu, 11 Jul 2019 10:13:02 +0100 Subject: [PATCH 269/447] Correctly specify test dependencies. --- setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index e99dc608..097d352c 100644 --- a/setup.py +++ b/setup.py @@ -6,11 +6,11 @@ requires=[ 'pyluach', ], - # install_requires=[ - # 'pandas', - # 'pytest-cov', - # 'pytest-flake8', - # ], + tests_require=[ + 'pandas', + 'pytest-cov', + 'pytest-flake8', + ], ) if __name__ == "__main__": From 9bb47676e98d8698c081f5bb6f8f1b25e5f0f8fc Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Thu, 11 Jul 2019 10:15:28 +0100 Subject: [PATCH 270/447] Run setup.py's content just as it was originally done. --- setup.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 097d352c..5f03258e 100644 --- a/setup.py +++ b/setup.py @@ -2,16 +2,16 @@ import setuptools -setuptools.setup( - requires=[ - 'pyluach', - ], - tests_require=[ - 'pandas', - 'pytest-cov', - 'pytest-flake8', - ], -) if __name__ == "__main__": - setuptools.setup(use_scm_version=True) + setuptools.setup( + use_scm_version=True, + requires=[ + 'pyluach', + ], + tests_require=[ + 'pandas', + 'pytest-cov', + 'pytest-flake8', + ], + ) From 272e1dea307c5ed78535ef709e96db80774f22c8 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Thu, 11 Jul 2019 10:28:13 +0100 Subject: [PATCH 271/447] Try adding pyluach as a dependency in tox.ini to see if it fixes Travis. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 70b0be7a..170bc2c8 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ minversion = 2.4 [testenv] deps = setuptools>=31.0.1 + pyluach commands = pytest {posargs} usedevelop = True From ca69c2fc1167e36123906946094701fddb76992e Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Thu, 11 Jul 2019 10:43:00 +0100 Subject: [PATCH 272/447] Eliminate a cosmetic change I introduced. --- calendra/europe/united_kingdom.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/calendra/europe/united_kingdom.py b/calendra/europe/united_kingdom.py index a3ae9d24..2b0d7597 100644 --- a/calendra/europe/united_kingdom.py +++ b/calendra/europe/united_kingdom.py @@ -18,13 +18,13 @@ class UnitedKingdom(WesternCalendar, ChristianMixin): include_boxing_day = True shift_new_years_day = True non_computable_holiday_dict = { - 1973: [(date(1973, 11, 14), "Royal wedding")], - 1977: [(date(1977, 6, 7), "Queen’s Silver Jubilee")], - 1981: [(date(1981, 7, 29), "Royal wedding")], - 1999: [(date(1999, 12, 31), "New Year's Eve")], - 2002: [(date(2002, 6, 3), "Queen’s Golden Jubilee")], - 2011: [(date(2011, 4, 29), "Royal Wedding")], - 2012: [(date(2012, 6, 5), "Queen’s Diamond Jubilee")], + 1973: [(date(1973, 11, 14), "Royal wedding"), ], + 1977: [(date(1977, 6, 7), "Queen’s Silver Jubilee"), ], + 1981: [(date(1981, 7, 29), "Royal wedding"), ], + 1999: [(date(1999, 12, 31), "New Year's Eve"), ], + 2002: [(date(2002, 6, 3), "Queen’s Golden Jubilee"), ], + 2011: [(date(2011, 4, 29), "Royal Wedding"), ], + 2012: [(date(2012, 6, 5), "Queen’s Diamond Jubilee"), ], } def get_early_may_bank_holiday(self, year): From ddcf6cce30f6014f1e8486911a3b776f767fddd9 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Thu, 11 Jul 2019 11:02:46 +0100 Subject: [PATCH 273/447] Restore change entry dropped in error. --- CHANGES.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0e4e2fc0..23f8d186 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,10 +4,11 @@ Incorporate changes from workalendar v5.2.2. (2019-07-07) +- **Deprecation Warning:** *Currently the registry returns `OrderedDict` objects when you're querying for regions or subregions. Expect that the next major release will preferrably return plain'ol' `dict` objects. If your scripts rely on the order of the objects returned, you'll have to sort them yourself.* - Fix Denmark, remove observances (remove Palm Sunday, Constitution Day, Christmas Eve and New Year's Eve) (#387, #386) Incorporate changes from workalendar v5.2.1 (2019-07-05) - + - Refactored the package building procedure, now linked to `make package` ; added a note about this target in the PR template (#366). - Fixed United Kingom's 2020 holidays ; The Early May Bank Holiday has been moved to May 8th to commemorate the 75th anniversary of the end of WWII (#381). From 685f253d6856376a0c1f4ff63949a4230fa2c733 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Mon, 12 Aug 2019 18:11:35 +0100 Subject: [PATCH 274/447] Start to address feedback comments. --- CHANGES.rst | 2 -- CONTRIBUTING.rst | 7 +++++-- calendra/europe/united_kingdom.py | 11 +++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 23f8d186..cba168ed 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -30,8 +30,6 @@ Incorporate changes from workalendar v5.1.1 (2019-06-27) Incorporate changes from workalendar v5.1.0 (2019-06-24) -- **Deprecation Warning:** *Currently the registry returns `OrderedDict` objects when you're querying for regions or subregions. Expect that the next major release will preferrably return plain'ol' `dict` objects. If your scripts rely on the order of the objects returned, you'll have to sort them yourself.* - - New Calendar - Added Turkey by @tayyipgoren (#371). diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 58fc49ec..9efa7780 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -274,8 +274,11 @@ Syncing from upstream Workalendar git diff 5.2.2..HEAD -6. Proceed with care, and test as needed:: +6. Merge, based on the diff command above, and a careful review/tweaking + of the results. + +7. Proceed with care, and test as needed:: tox -7. Generate a PR when ready! +8. Generate a PR when ready! diff --git a/calendra/europe/united_kingdom.py b/calendra/europe/united_kingdom.py index 2b0d7597..7466b687 100644 --- a/calendra/europe/united_kingdom.py +++ b/calendra/europe/united_kingdom.py @@ -38,12 +38,11 @@ def get_early_may_bank_holiday(self, year): "Early May bank holiday (VE day)", indication="VE day", ) - else: - return Holiday( - UnitedKingdom.get_nth_weekday_in_month(year, 5, MON), - "Early May Bank Holiday", - indication="1st Monday in May", - ) + return Holiday( + UnitedKingdom.get_nth_weekday_in_month(year, 5, MON), + "Early May Bank Holiday", + indication="1st Monday in May", + ) def get_spring_bank_holiday(self, year): if year == 2012: From 690dcf0b1727d6746d385581b0f9fcff3797c1f6 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Mon, 12 Aug 2019 18:39:01 +0100 Subject: [PATCH 275/447] Remove unused function as per feedback. --- calendra/usa/core.py | 49 -------------------------------------------- 1 file changed, 49 deletions(-) diff --git a/calendra/usa/core.py b/calendra/usa/core.py index fbc4853d..76c998c2 100644 --- a/calendra/usa/core.py +++ b/calendra/usa/core.py @@ -84,55 +84,6 @@ def observance_shift(self): # (11, 11), # Veterans day won't be shifted ) - def shift(self, holidays, year): - new_holidays = [] - holiday_lookup = [x[0] for x in holidays] - exceptions = [ - date(year, month, day) for month, day in self.shift_exceptions - ] - - # For each holiday available: - # * if it falls on SUN, add the observed on MON - # * if it falls on SAT, add the observed on FRI - for day, label in holidays: - # ... except if it's been explicitely excepted. - if day in exceptions: - continue - if day.weekday() == SAT: - new_holidays.append((day - timedelta(days=1), - label + " (Observed)")) - elif day.weekday() == SUN: - new_holidays.append((day + timedelta(days=1), - label + " (Observed)")) - - # If year+1 January the 1st is on SAT, add the FRI before to observed - if date(year + 1, 1, 1).weekday() == SAT: - new_holidays.append((date(year, 12, 31,), - "New Years Day (Observed)")) - - # Special rules for XMas and XMas Eve - christmas = date(year, 12, 25) - christmas_eve = date(year, 12, 24) - # Is XMas eve in your calendar? - if christmas_eve in holiday_lookup: - # You are observing the THU before, as an extra XMas Eve - if christmas.weekday() == SAT: - # Remove the "fake" XMAS Day shift, the one done before. - new_holidays.remove( - (christmas_eve, "Christmas Day (Observed)") - ) - new_holidays.append((date(year, 12, 23), - "Christmas Eve (Observed)")) - # You are observing the 26th (TUE) - elif christmas.weekday() == MON: - # Remove the "fake" XMAS Eve shift, done before - new_holidays.remove( - (christmas, "Christmas Eve (Observed)") - ) - new_holidays.append((date(year, 12, 26), - "Christmas Day (Observed)")) - return holidays + new_holidays - @staticmethod def is_presidential_year(year): return (year % 4) == 0 From 524cd0cda16ad5aba84fcefddf2c066b4a1fa1ee Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 12 Aug 2019 08:01:47 -0400 Subject: [PATCH 276/447] Remove duplicate 'dist' declaration --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5bafe53c..f5c09d96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ dist: xenial sudo: false -dist: xenial language: python python: From 20964edcfd5b35c6006bc6425a5587004ed76eec Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Aug 2019 22:39:34 -0400 Subject: [PATCH 277/447] Define passenv in tox release section. Rely on __token__ for default username. --- .travis.yml | 5 ----- tox.ini | 4 ++++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17d02624..6ccac8f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,11 +13,6 @@ jobs: if: tag IS present python: *latest_py3 before_script: skip - env: - - TWINE_USERNAME=jaraco - # TWINE_PASSWORD - - secure: ... - - TOX_TESTENV_PASSENV="TWINE_USERNAME TWINE_PASSWORD" script: tox -e release cache: pip diff --git a/tox.ini b/tox.ini index 5f1d1b7f..4f3341f8 100644 --- a/tox.ini +++ b/tox.ini @@ -26,6 +26,10 @@ deps = pep517>=0.5 twine>=1.13 path.py +passenv = + TWINE_PASSWORD +setenv = + TWINE_USERNAME = {env:TWINE_USERNAME:__token__} commands = python -c "import path; path.Path('dist').rmtree_p()" python -m pep517.build . From 4baec898fdfef6da27653d21fdf223da10b13342 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 22 Aug 2019 16:40:51 -0400 Subject: [PATCH 278/447] Update docs to reflect changes to deployment. --- skeleton.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/skeleton.md b/skeleton.md index 72494070..52b97f09 100644 --- a/skeleton.md +++ b/skeleton.md @@ -103,13 +103,7 @@ Relies a .flake8 file to correct some default behaviors: ## Continuous Integration -The project is pre-configured to run tests in [Travis-CI](https://travis-ci.org) (.travis.yml). Any new project must be enabled either through their web site or with the `travis enable` command. In addition to running tests, an additional deploy stage is configured to automatically release tagged commits. The username and password for PyPI must be configured for each project using the `travis` command and only after the travis project is created. As releases are cut with [twine](https://pypi.org/project/twine), the two values are supplied through the `TWINE_USERNAME` and `TWINE_PASSWORD`. To configure the latter as a secret, run the following command: - -``` -echo "TWINE_PASSWORD={password}" | travis encrypt -``` - -Or disable it in the CI definition and configure it through the web UI. +The project is pre-configured to run tests in [Travis-CI](https://travis-ci.org) (.travis.yml). Any new project must be enabled either through their web site or with the `travis enable` command. Features include: - test against Python 2 and 3 @@ -118,6 +112,14 @@ Features include: Also provided is a minimal template for running under Appveyor (Windows). +### Continuous Deployments + +In addition to running tests, an additional deploy stage is configured to automatically release tagged commits to PyPI using [API tokens](https://pypi.org/help/#apitoken). The release process expects an authorized token to be configured with Travis as the TWINE_PASSWORD environment variable. After the Travis project is created, configure the token through the web UI or with a command like the following (bash syntax): + +``` +TWINE_PASSWORD={token} travis env copy TWINE_PASSWORD +``` + ## Building Documentation Documentation is automatically built by [Read the Docs](https://readthedocs.org) when the project is registered with it, by way of the .readthedocs.yml file. To test the docs build manually, a tox env may be invoked as `tox -e build-docs`. Both techniques rely on the dependencies declared in `setup.cfg/options.extras_require.docs`. @@ -127,3 +129,9 @@ In addition to building the sphinx docs scaffolded in `docs/`, the docs build a ## Cutting releases By default, tagged commits are released through the continuous integration deploy stage. + +Releases may also be cut manually by invoking the tox environment `release` with the PyPI token set as the TWINE_PASSWORD: + +``` +TWINE_PASSWORD={token} tox -e release +``` From 05a3c52b4d41690e0471a2e283cffb500dc0329a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 11 Sep 2019 11:25:50 +0100 Subject: [PATCH 279/447] Python 3 only --- .travis.yml | 1 - appveyor.yml | 2 +- setup.cfg | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6ccac8f2..8b607a65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ dist: xenial language: python python: -- 2.7 - 3.6 - &latest_py3 3.7 diff --git a/appveyor.yml b/appveyor.yml index f35aa27d..bfd57529 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ environment: matrix: - PYTHON: "C:\\Python36-x64" - - PYTHON: "C:\\Python27-x64" + - PYTHON: "C:\\Python37-x64" install: # symlink python from a directory with a space diff --git a/setup.cfg b/setup.cfg index 9345869b..8dc6d4ec 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,13 +13,12 @@ classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers License :: OSI Approved :: MIT License - Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 [options] packages = find: include_package_data = true -python_requires = >=2.7 +python_requires = >=3.6 install_requires = setup_requires = setuptools_scm >= 1.15.0 From a28efc59c12e16a02ec98659b660e5b5809af650 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 15 Sep 2019 21:36:25 +0200 Subject: [PATCH 280/447] Enable coverage reporting on project --- .coveragerc | 2 ++ pytest.ini | 2 +- setup.cfg | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..896b501e --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = .tox/* diff --git a/pytest.ini b/pytest.ini index 10681adf..a86fb660 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] norecursedirs=dist build .tox .eggs -addopts=--doctest-modules --flake8 --black +addopts=--doctest-modules --flake8 --black --cov doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= ignore:Possible nested set::pycodestyle:113 diff --git a/setup.cfg b/setup.cfg index 9345869b..77df5051 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,7 @@ testing = pytest-checkdocs pytest-flake8 pytest-black-multipy + pytest-cov # local From cc1a1c9be39ba29e90d6d9d8ab5d6d1768a50594 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 22 Sep 2019 14:25:32 +0200 Subject: [PATCH 281/447] Report the lines missing coverage --- .coveragerc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.coveragerc b/.coveragerc index 896b501e..45823064 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,5 @@ [run] omit = .tox/* + +[report] +show_missing = True From 9314eb458311dfd8981a6378b8498017c89ea2f7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 29 Sep 2019 22:46:03 -0400 Subject: [PATCH 282/447] Ensure that a late version of pip is installed without special versions of tox-venv. --- tox.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tox.ini b/tox.ini index 4f3341f8..ab6cd407 100644 --- a/tox.ini +++ b/tox.ini @@ -3,10 +3,16 @@ envlist = python minversion = 3.2 # https://github.com/jaraco/skeleton/issues/6 tox_pip_extensions_ext_venv_update = true +# ensure that a late version of pip is used even on tox-venv +requires = + tox-pip-version + tox-venv + [testenv] deps = setuptools>=31.0.1 +pip_version = pip commands = pytest {posargs} usedevelop = True From 1cfcc4214082bcb6bec9ea51ad91f15446488e9b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 20 Oct 2019 17:25:23 -0400 Subject: [PATCH 283/447] Disable tox-pip-version as it interacts badly with tox-venv causing tox to use the wrong Python version to install packages and run tests. Ref pglass/tox-pip-version#20 and tox-dev/tox-venv#40. --- tox.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index ab6cd407..1d81b812 100644 --- a/tox.ini +++ b/tox.ini @@ -3,9 +3,10 @@ envlist = python minversion = 3.2 # https://github.com/jaraco/skeleton/issues/6 tox_pip_extensions_ext_venv_update = true -# ensure that a late version of pip is used even on tox-venv +# Ensure that a late version of pip is used even on tox-venv. +# Disabled due to pglass/tox-pip-version#20. requires = - tox-pip-version +# tox-pip-version tox-venv From c169e5e50fd5f18dfe554d06bfe3940cc950b13e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 25 Oct 2019 19:39:34 -0400 Subject: [PATCH 284/447] Bring back tox-pip-version now that pglass/tox-pip-version#20 is fixed. --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 1d81b812..d267e16d 100644 --- a/tox.ini +++ b/tox.ini @@ -4,9 +4,8 @@ minversion = 3.2 # https://github.com/jaraco/skeleton/issues/6 tox_pip_extensions_ext_venv_update = true # Ensure that a late version of pip is used even on tox-venv. -# Disabled due to pglass/tox-pip-version#20. requires = -# tox-pip-version + tox-pip-version>=0.0.6 tox-venv From bd4b0a9e35ae199b839fb46c005e9d4704a91e6a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 10:34:30 -0400 Subject: [PATCH 285/447] Prefer 'relativedelta' for computing relative dates. Restore 'observance' dates for UK holidays. --- calendra/europe/united_kingdom.py | 46 +++++++++++++------------------ calendra/tests/test_europe.py | 8 ++++-- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/calendra/europe/united_kingdom.py b/calendra/europe/united_kingdom.py index 7466b687..8fa1d707 100644 --- a/calendra/europe/united_kingdom.py +++ b/calendra/europe/united_kingdom.py @@ -2,9 +2,10 @@ from __future__ import unicode_literals from datetime import date +from dateutil import relativedelta as rd + from ..core import WesternCalendar, ChristianMixin from ..core import Holiday -from ..core import MON from ..registry_tools import iso_register @@ -31,38 +32,32 @@ def get_early_may_bank_holiday(self, year): """ Return Early May bank holiday """ + day = date(year, 5, 1) + rd.relativedelta(weekday=rd.MO(1)) + desc = "Early May Bank Holiday" + indication = "1st Monday in May" + # Special case in 2020, for the 75th anniversary of the end of WWII. if year == 2020: - return Holiday( - date(year, 5, 8), - "Early May bank holiday (VE day)", - indication="VE day", - ) - return Holiday( - UnitedKingdom.get_nth_weekday_in_month(year, 5, MON), - "Early May Bank Holiday", - indication="1st Monday in May", - ) + day = date(year, 5, 8) + desc += " (VE day)" + indication = "VE day" + return Holiday(day, desc, indication=indication) def get_spring_bank_holiday(self, year): - if year == 2012: - spring_bank_holiday = date(2012, 6, 4) - elif year == 1977: - spring_bank_holiday = date(1977, 6, 6) - elif year == 2002: - spring_bank_holiday = date(2002, 6, 4) - else: - spring_bank_holiday = UnitedKingdom. \ - get_last_weekday_in_month(year, 5, MON) + day = date(year, 5, 30) + rd.relativedelta(weekday=rd.MO(-1)) + if year in (2012, 2002): + day = date(year, 6, 4) + if year in (1977,): + day = date(year, 6, 6) return Holiday( - spring_bank_holiday, + day, "Spring Bank Holiday", - indication="Last Monday in May" - ) + indication="Last Monday in May", + ), def get_late_summer_bank_holiday(self, year): return Holiday( - UnitedKingdom.get_last_weekday_in_month(year, 8, MON), + date(year, 8, 31) + rd.relativedelta(weekday=rd.MO(-1)), "Late Summer Bank Holiday", indication="Last Monday in August", ) @@ -76,9 +71,6 @@ def get_variable_days(self, year): days.append(self.get_early_may_bank_holiday(year)) days.append(self.get_spring_bank_holiday(year)) days.append(self.get_late_summer_bank_holiday(year)) - # Boxing day & XMas shift - shifts = self.shift_christmas_boxing_days(year=year) - days.extend(shifts) non_computable = self.non_computable_holiday(year) if non_computable: days.extend(non_computable) diff --git a/calendra/tests/test_europe.py b/calendra/tests/test_europe.py index a33ea77b..95193ee4 100644 --- a/calendra/tests/test_europe.py +++ b/calendra/tests/test_europe.py @@ -936,15 +936,17 @@ def test_2020(self): self.assertIn(date(2020, 5, 25), holidays) # Spring Bank Holiday self.assertIn(date(2020, 8, 31), holidays) # Late Summer Bank Holiday self.assertIn(date(2020, 12, 25), holidays) # Christmas Day - self.assertIn(date(2020, 12, 26), holidays) # 'Boxing Day - self.assertIn(date(2020, 12, 28), holidays) # Boxing Day Shift + self.assertIn(date(2020, 12, 26), holidays) # Boxing Day + + # Boxing observed + assert self.cal.is_observed_holiday(date(2020, 12, 28)) # May the 8th is VE day holidays = self.cal.holidays(2020) holidays = dict(holidays) self.assertIn(date(2020, 5, 8), holidays) self.assertEqual( - holidays[date(2020, 5, 8)], "Early May bank holiday (VE day)" + holidays[date(2020, 5, 8)], "Early May Bank Holiday (VE day)" ) From dae5cbb59488f66970a89ed4cb71f5d27b75807f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 11:26:51 -0400 Subject: [PATCH 286/447] Update New Zealand to use relativedelta and Holiday features. --- calendra/oceania/new_zealand.py | 83 ++++++++++----------------------- calendra/tests/test_oceania.py | 21 +++++---- 2 files changed, 35 insertions(+), 69 deletions(-) diff --git a/calendra/oceania/new_zealand.py b/calendra/oceania/new_zealand.py index 85781ad4..b8f4d77d 100644 --- a/calendra/oceania/new_zealand.py +++ b/calendra/oceania/new_zealand.py @@ -1,87 +1,52 @@ # -*- coding: utf-8 -*- -from datetime import date, timedelta +from datetime import date + +from dateutil import relativedelta as rd from ..core import WesternCalendar, ChristianMixin -from ..core import MON, SAT, SUN +from ..core import Holiday from ..registry_tools import iso_register +def _by_name(holidays): + return {day.name: day for day in holidays if hasattr(day, 'name')} + + @iso_register("NZ") class NewZealand(WesternCalendar, ChristianMixin): "New Zealand" include_good_friday = True include_easter_monday = True include_boxing_day = True + shift_new_years_day = True FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( - (1, 2, "Day after New Year's Day"), - (2, 6, "Waitangi Day"), - (4, 25, "ANZAC Day") + Holiday(date(2000, 2, 6), "Waitangi Day"), + Holiday(date(2000, 4, 25), "ANZAC Day"), ) def get_queens_birthday(self, year): - return ( - NewZealand.get_nth_weekday_in_month(year, 6, MON, 1), - "Queen's Birthday" + return Holiday( + date(year, 6, 1) + rd.relativedelta(weekday=rd.MO(1)), + "Queen's Birthday", + indication="First Monday in June", ) def get_labour_day(self, year): - return ( - NewZealand.get_nth_weekday_in_month(year, 10, MON, 4), - "Labour Day" + return Holiday( + date(year, 10, 1) + rd.relativedelta(weekday=rd.MO(4)), + "Labour Day", + indication="Fourth Monday in October", ) def get_variable_days(self, year): # usual variable days days = super(NewZealand, self).get_variable_days(year) + days.append(Holiday( + date(year, 1, 2), + "Day after New Year's Day", + observe_after=_by_name(days)["New year"], + )) days.append(self.get_queens_birthday(year)) days.append(self.get_labour_day(year)) - - waitangi_day = date(year, 2, 6) - if waitangi_day.weekday() in self.get_weekend_days(): - days.append(( - self.find_following_working_day(waitangi_day), - "Waitangi Day Shift") - ) - - anzac_day = date(year, 4, 25) - if anzac_day.weekday() in self.get_weekend_days(): - days.append(( - self.find_following_working_day(anzac_day), - "ANZAC Day Shift") - ) - - christmas = date(year, 12, 25) - boxing_day = date(year, 12, 26) - if christmas.weekday() is SAT: - shift = self.find_following_working_day(christmas) - days.append((shift, "Christmas Shift")) - elif christmas.weekday() is SUN: - shift = self.find_following_working_day(christmas) - days.append((shift + timedelta(days=1), "Christmas Shift")) - - if boxing_day.weekday() is SAT: - shift = self.find_following_working_day(boxing_day) - days.append((shift, "Boxing Day Shift")) - elif boxing_day.weekday() is SUN: - shift = self.find_following_working_day(boxing_day) - days.append((shift + timedelta(days=1), "Boxing Day Shift")) - - new_year = date(year, 1, 1) - day_after_new_year = date(year, 1, 2) - if new_year.weekday() is SAT: - shift = self.find_following_working_day(new_year) - days.append((shift, "New Year Shift")) - elif new_year.weekday() is SUN: - shift = self.find_following_working_day(new_year) - days.append((shift + timedelta(days=1), "New Year Shift")) - - if day_after_new_year.weekday() is SAT: - shift = self.find_following_working_day(day_after_new_year) - days.append((shift, "Day after New Year's Day Shift")) - elif day_after_new_year.weekday() is SUN: - shift = self.find_following_working_day(day_after_new_year) - days.append((shift + timedelta(days=1), - "Day after New Year's Day Shift")) - return days diff --git a/calendra/tests/test_oceania.py b/calendra/tests/test_oceania.py index 8e0b7d50..5220e901 100644 --- a/calendra/tests/test_oceania.py +++ b/calendra/tests/test_oceania.py @@ -290,37 +290,38 @@ def test_new_year_shift(self): # New Years was on a sunday # Day After New Years is on the 2nd self.assertIn(date(2012, 1, 2), holidays) - # New Years Shift is on the 3rd - self.assertIn(date(2012, 1, 3), holidays) + # New Years observed on the 3rd + assert self.cal.is_observed_holiday(date(2012, 1, 3)) def test_anzac_shift(self): holidays = self.cal.holidays_set(2010) # 25th was a sunday # ANZAC Day is on 25th self.assertIn(date(2010, 4, 25), holidays) - # ANZAC Day Shift is on 26th - self.assertIn(date(2010, 4, 26), holidays) + # ANZAC Day observed on 26th + assert self.cal.is_observed_holiday(date(2010, 4, 26)) def test_waitangi_shift(self): holidays = self.cal.holidays_set(2016) # 6th was a saturday # Waitangi Day is on 6th self.assertIn(date(2016, 2, 6), holidays) - # Waitangi Day Shift is on 7th - self.assertIn(date(2016, 2, 8), holidays) + # Waitangi Day observed on 8th + assert self.cal.is_observed_holiday(date(2016, 2, 8)) def test_oceania_shift_2016(self): holidays = self.cal.holidays_set(2016) # Christmas day is on sunday in 2016 # Boxing day is on 26th self.assertIn(date(2016, 12, 26), holidays) - # Christmas day shift on 27th - self.assertIn(date(2016, 12, 27), holidays) + # Christmas day observed on 26th and boxing day on 27th + assert self.cal.is_observed_holiday(date(2016, 12, 26)) + assert self.cal.is_observed_holiday(date(2016, 12, 27)) def test_oceania_shift_2009(self): holidays = self.cal.holidays_set(2009) # Boxing day is on saturday in 2009 # Boxing day is on 26th self.assertIn(date(2009, 12, 26), holidays) - # Boxing day shift on 28th - self.assertIn(date(2009, 12, 28), holidays) + # Boxing day is observed on 28th + assert self.cal.is_observed_holiday(date(2009, 12, 28)) From 315f940c616897d014308d049016767c1a622e16 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 11:30:24 -0400 Subject: [PATCH 287/447] Move pyluach into a simple dependency. --- setup.cfg | 1 + setup.py | 13 +------------ tox.ini | 1 - 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/setup.cfg b/setup.cfg index e3ca11cc..32cdece2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,7 @@ install_requires = pyCalverter more_itertools ephem + pyluach setup_requires = setuptools_scm >= 1.15.0 [options.extras_require] diff --git a/setup.py b/setup.py index 5f03258e..827e955f 100644 --- a/setup.py +++ b/setup.py @@ -2,16 +2,5 @@ import setuptools - if __name__ == "__main__": - setuptools.setup( - use_scm_version=True, - requires=[ - 'pyluach', - ], - tests_require=[ - 'pandas', - 'pytest-cov', - 'pytest-flake8', - ], - ) + setuptools.setup(use_scm_version=True) diff --git a/tox.ini b/tox.ini index 170bc2c8..70b0be7a 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,6 @@ minversion = 2.4 [testenv] deps = setuptools>=31.0.1 - pyluach commands = pytest {posargs} usedevelop = True From 044e2b3a23186e85d702e020d1849d52e2c9f61b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 11:49:35 -0400 Subject: [PATCH 288/447] Disable black as it will drastically impede merging with upstream. --- README.rst | 6 +++--- pytest.ini | 2 +- setup.cfg | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 0eb22812..ca28e43e 100644 --- a/README.rst +++ b/README.rst @@ -6,9 +6,9 @@ .. image:: https://img.shields.io/travis/jaraco/calendra/master.svg :target: https://travis-ci.org/jaraco/calendra -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/ambv/black - :alt: Code style: Black +.. .. image:: https://img.shields.io/badge/code%20style-black-000000.svg +.. :target: https://github.com/ambv/black +.. :alt: Code style: Black .. .. image:: https://img.shields.io/appveyor/ci/jaraco/calendra/master.svg .. :target: https://ci.appveyor.com/project/jaraco/calendra/branch/master diff --git a/pytest.ini b/pytest.ini index a86fb660..0b3cdd70 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] norecursedirs=dist build .tox .eggs -addopts=--doctest-modules --flake8 --black --cov +addopts=--doctest-modules --flake8 --cov doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= ignore:Possible nested set::pycodestyle:113 diff --git a/setup.cfg b/setup.cfg index 2b6355f5..f3b66ec9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,7 +36,8 @@ testing = pytest >= 3.5, !=3.7.3 pytest-checkdocs pytest-flake8 - pytest-black-multipy + # disabled for easier merging + # pytest-black-multipy pytest-cov # local From 5ac2ef95d887f0e50e07e3d4e73d06b99b778051 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 11:51:07 -0400 Subject: [PATCH 289/447] Use semver numbers and tags. --- CHANGES.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index cba168ed..dab98130 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,6 @@ -4.0 ---- +v4.0.0 +------ Incorporate changes from workalendar v5.2.2. (2019-07-07) From 34ecf58479ad45fc6bfa5c8b476b719cf5720c14 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 12:30:25 -0400 Subject: [PATCH 290/447] Test/release on Python 3.8 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6ccac8f2..b7d8f3ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ language: python python: - 2.7 - 3.6 -- &latest_py3 3.7 +- &latest_py3 3.8 jobs: fast_finish: true From eaeb9ec2eb542a04948c6c9742fb90069f37de33 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 13:29:45 -0400 Subject: [PATCH 291/447] Apply black to docs/conf.py --- docs/conf.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 77cef345..41b53557 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,19 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -extensions = [ - 'sphinx.ext.autodoc', - 'jaraco.packaging.sphinx', - 'rst.linker', -] +extensions = ['sphinx.ext.autodoc', 'jaraco.packaging.sphinx', 'rst.linker'] master_doc = "index" link_files = { '../CHANGES.rst': dict( - using=dict( - GH='https://github.com', - ), + using=dict(GH='https://github.com'), replace=[ dict( pattern=r'(Issue #|\B#)(?P\d+)', @@ -28,5 +22,5 @@ url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/', ), ], - ), + ) } From 174f0fd7cf349c277ade401ddb88dde530723053 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 27 Oct 2019 13:36:04 -0400 Subject: [PATCH 292/447] Update black version and links --- .pre-commit-config.yaml | 4 ++-- README.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 922d9424..e16c59ac 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ repos: -- repo: https://github.com/ambv/black - rev: 18.9b0 +- repo: https://github.com/psf/black + rev: 19.3b0 hooks: - id: black diff --git a/README.rst b/README.rst index 7050da33..50eba567 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,7 @@ :target: https://travis-ci.org/jaraco/skeleton .. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/ambv/black + :target: https://github.com/psf/black :alt: Code style: Black .. .. image:: https://img.shields.io/appveyor/ci/jaraco/skeleton/master.svg From 03d825d44a003d08cb54708fd87fccd161806d3f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 31 Oct 2019 16:14:40 -0400 Subject: [PATCH 293/447] Expect flake8 3.6 or later and remove suppression of warnings from Flake8 prior to 3.6. --- pytest.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/pytest.ini b/pytest.ini index a86fb660..54f1b9aa 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,8 +3,6 @@ norecursedirs=dist build .tox .eggs addopts=--doctest-modules --flake8 --black --cov doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= - ignore:Possible nested set::pycodestyle:113 - ignore:Using or importing the ABCs::flake8:410 # workaround for https://sourceforge.net/p/docutils/bugs/348/ ignore:'U' mode is deprecated::docutils.io # workaround for https://gitlab.com/pycqa/flake8/issues/275 From 48d361a4146d6bbc52c96718ec83cf56410af73a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 31 Oct 2019 16:30:21 -0400 Subject: [PATCH 294/447] Rely on pytest-checkdocs 1.2.3, eliminating workaround for docutils warning. --- pytest.ini | 2 -- setup.cfg | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pytest.ini b/pytest.ini index 54f1b9aa..4ffa6f29 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,7 +3,5 @@ norecursedirs=dist build .tox .eggs addopts=--doctest-modules --flake8 --black --cov doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= - # workaround for https://sourceforge.net/p/docutils/bugs/348/ - ignore:'U' mode is deprecated::docutils.io # workaround for https://gitlab.com/pycqa/flake8/issues/275 ignore:You passed a bytestring as `filenames`.::flake8 diff --git a/setup.cfg b/setup.cfg index 77df5051..63b865ca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,7 +27,7 @@ setup_requires = setuptools_scm >= 1.15.0 testing = # upstream pytest >= 3.5, !=3.7.3 - pytest-checkdocs + pytest-checkdocs >= 1.2.3 pytest-flake8 pytest-black-multipy pytest-cov From f10294a99395a64370695e43521175bb93e4b4f3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Nov 2019 11:54:25 -0400 Subject: [PATCH 295/447] Remove workaround for gitlab.com/pycqa/flake8/issues/275, apparently no longer necessary. --- pytest.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/pytest.ini b/pytest.ini index 4ffa6f29..7b9b714f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,5 +3,3 @@ norecursedirs=dist build .tox .eggs addopts=--doctest-modules --flake8 --black --cov doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= - # workaround for https://gitlab.com/pycqa/flake8/issues/275 - ignore:You passed a bytestring as `filenames`.::flake8 From 8e7c267538204284067e1fa70d53fce9f78c60f7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 2 Nov 2019 22:11:19 -0500 Subject: [PATCH 296/447] Normalize indentation --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index d267e16d..64eb21d5 100644 --- a/tox.ini +++ b/tox.ini @@ -20,11 +20,11 @@ extras = testing [testenv:build-docs] extras = - docs - testing + docs + testing changedir = docs commands = - python -m sphinx . {toxinidir}/build/html + python -m sphinx . {toxinidir}/build/html [testenv:release] skip_install = True From a0651976d78d84a22a5d06807d46194218a1fefa Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 7 Nov 2019 10:54:31 -0800 Subject: [PATCH 297/447] Include keyring support from twine --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 64eb21d5..d86e4ad9 100644 --- a/tox.ini +++ b/tox.ini @@ -30,7 +30,7 @@ commands = skip_install = True deps = pep517>=0.5 - twine>=1.13 + twine[keyring]>=1.13 path.py passenv = TWINE_PASSWORD From a6da513be56015a0c37ffd096dbc6b99c4a8bd71 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Fri, 8 Nov 2019 14:15:17 +0000 Subject: [PATCH 298/447] Replace ephem with skyfield and skyfield-data. --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f3b66ec9..c8e47b3d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,7 +26,8 @@ install_requires = pytz pyCalverter more_itertools - ephem + skyfield + skyfield-data pyluach setup_requires = setuptools_scm >= 1.15.0 From 87cf8e8ac3cefb2bd8dce8ecf8465964ea879a18 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Fri, 8 Nov 2019 14:20:32 +0000 Subject: [PATCH 299/447] Fix import. --- calendra/tests/test_astronomy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/calendra/tests/test_astronomy.py b/calendra/tests/test_astronomy.py index 5e56e7d7..5a572514 100644 --- a/calendra/tests/test_astronomy.py +++ b/calendra/tests/test_astronomy.py @@ -1,6 +1,6 @@ from datetime import date -from workalendar.astronomy import calculate_equinoxes, solar_term +from ..astronomy import calculate_equinoxes, solar_term def test_calculate_some_equinoxes(): From af1dfacdfa10e64e08bfdf9cea5ce615ae380123 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Fri, 8 Nov 2019 14:31:53 +0000 Subject: [PATCH 300/447] Unbreak core.py doctests. --- calendra/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/calendra/core.py b/calendra/core.py index eb405c37..fd1e364f 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -472,9 +472,10 @@ def get_working_days_delta(self, start, end, include_start=False): Example: + >>> from dateutil.parser import parse >>> cal = France() - >>> day1 = parse('09/05/2018 00:01', dayfirst=True) - >>> day2 = parse('10/05/2018 19:01', dayfirst=True) # holiday in france + >>> day_1 = parse('09/05/2018 00:01', dayfirst=True) + >>> day_2 = parse('10/05/2018 19:01', dayfirst=True) # holiday in france >>> cal.get_working_days_delta(day_1, day_2) 0 From 16746059fcfe7a7f9cf8341f06aa918f32d19295 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Nov 2019 11:34:48 -0800 Subject: [PATCH 301/447] Just one major bump is sufficient; tracking workalendar versions leads to problems. --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index c6a2d3f6..d8261dd4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,4 +1,4 @@ -v7.0.0 +v5.0.0 ------ Incorporate changes from workalendar v7.0.0 (2019-09-20) From 574746efe0d1185a3180e2a465210496b0519a20 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Nov 2019 13:30:33 -0800 Subject: [PATCH 302/447] Add __add__ and __sub__ to Holiday instances to improve compatibility on Python 3.8. Fixes #11. --- calendra/core.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/calendra/core.py b/calendra/core.py index fd1e364f..0bf2edeb 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -95,6 +95,14 @@ def replace(self, *args, **kwargs): vars(replaced).update(vars(self)) return replaced + def __add__(self, other): + orig = date(self.year, self.month, self.day) + return Holiday(orig + other, **vars(self)) + + def __sub__(self, other): + orig = date(self.year, self.month, self.day) + return Holiday(orig - other, **vars(self)) + def nearest_weekday(self, calendar): """ Return the nearest weekday to self. From c665e8cebb06a304dc536cd82bf388de555458fb Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Nov 2019 13:35:35 -0800 Subject: [PATCH 303/447] Update changelog. Ref #11. --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d8261dd4..0e460937 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,10 @@ v5.0.0 ------ +#11: Add support for ``__add__`` and ``__sub__`` for +``Holiday`` instances on Python 3.8 and later. Now adding +a timedelta to a ``Holiday`` returns another ``Holiday``. + Incorporate changes from workalendar v7.0.0 (2019-09-20) - Drop `ephem` astronomical calculation library, in favor of `skyfield` and `skyfield-data` for providing minimal data files to enable computation (#302, #348). Many thanks to @GammaSagittarii for the tremendous help on finding the right way to compute Chinese Solar Terms. Also thanks to @antvig and @DainDwarf for testing the beta version (#398). From 0f901be8a9d74c351c5e952ba5dbe3545a00e9e4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Nov 2019 14:24:33 -0800 Subject: [PATCH 304/447] Convert Mexico to use holidays. --- calendra/america/mexico.py | 48 ++++++++++++++++------------------ calendra/tests/test_america.py | 20 +++++++------- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/calendra/america/mexico.py b/calendra/america/mexico.py index ee6816f2..8713e695 100644 --- a/calendra/america/mexico.py +++ b/calendra/america/mexico.py @@ -2,10 +2,12 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -from datetime import date, timedelta +from datetime import date + +from dateutil import relativedelta as rd from ..core import WesternCalendar, ChristianMixin -from ..core import SUN, MON, SAT +from ..core import Holiday from ..registry_tools import iso_register @@ -17,33 +19,27 @@ class Mexico(WesternCalendar, ChristianMixin): (9, 16, "Independence Day"), ) + shift_new_years_day = True + + @property + def observance_shift(self): + return Holiday.nearest_weekday + def get_variable_days(self, year): days = super(Mexico, self).get_variable_days(year) - days.append( - (Mexico.get_nth_weekday_in_month(year, 2, MON), - "Constitution Day")) + days.append(Holiday( + date(year, 2, 1) + rd.relativedelta(weekday=rd.MO(1)), + "Constitution Day", + )) - days.append( - (Mexico.get_nth_weekday_in_month(year, 3, MON, 3), - "Benito Juárez's birthday")) + days.append(Holiday( + date(year, 3, 1) + rd.relativedelta(weekday=rd.MO(3)), + "Benito Juárez's birthday", + )) - days.append( - (Mexico.get_nth_weekday_in_month(year, 11, MON, 3), - "Revolution Day")) - - return days + days.append(Holiday( + date(year, 11, 1) + rd.relativedelta(weekday=rd.MO(3)), + "Revolution Day", + )) - def get_calendar_holidays(self, year): - days = super(Mexico, self).get_calendar_holidays(year) - # If any statutory day is on Sunday, the monday is off - # If it's on a Saturday, the Friday is off - for day, label in days: - if day.weekday() == SAT: - days.append((day - timedelta(days=1), "%s substitute" % label)) - elif day.weekday() == SUN: - days.append((day + timedelta(days=1), "%s substitute" % label)) - # Extra: if new year's day is a saturday, the friday before is off - next_new_year = date(year + 1, 1, 1) - if next_new_year.weekday(): - days.append((date(year, 12, 31), "New Year Day substitute")) return days diff --git a/calendra/tests/test_america.py b/calendra/tests/test_america.py index 25940e50..5e5041fe 100644 --- a/calendra/tests/test_america.py +++ b/calendra/tests/test_america.py @@ -80,24 +80,26 @@ def test_holidays_2013(self): self.assertIn(date(2013, 12, 25), holidays) # XMas def test_shift_to_monday(self): - holidays = self.cal.holidays_set(2017) + observed = set(map(self.cal.get_observed_date, self.cal.holidays_set(2017))) # New year on Sunday -> shift - self.assertIn(date(2017, 1, 2), holidays) - holidays = self.cal.holidays_set(2016) + assert date(2017, 1, 2) in observed + observed = set(map(self.cal.get_observed_date, self.cal.holidays_set(2016))) # XMas on sunday -> shift to monday - self.assertIn(date(2016, 12, 26), holidays) + assert date(2016, 12, 26) in observed # Same for Labour day - self.assertIn(date(2016, 5, 2), holidays) + assert date(2016, 5, 2) in observed def test_shift_to_friday(self): - holidays = self.cal.holidays_set(2021) + holidays = self.cal.holidays_set(2021) | self.cal.holidays_set(2022) + observed = set(map(self.cal.get_observed_date, holidays)) # January 1st 2022 is a saturday, so we shift to friday - self.assertIn(date(2021, 12, 31), holidays) + assert date(2021, 12, 31) in observed # Same for Labour day - self.assertIn(date(2021, 4, 30), holidays) + assert date(2021, 4, 30) in observed holidays = self.cal.holidays_set(2021) + observed = set(map(self.cal.get_observed_date, holidays)) # December 25th, 2022 is a saturday, so we shift to friday - self.assertIn(date(2021, 12, 24), holidays) + assert date(2021, 12, 24) in observed class PanamaTest(GenericCalendarTest): From 45d68bc522c2c4715359aae5a02b4e29e918a31b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Nov 2019 18:49:22 -0800 Subject: [PATCH 305/447] Suppress warnings. Ref skyfielders/python-skyfield#286 --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest.ini b/pytest.ini index e7ee0faf..173f2b24 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,3 +3,5 @@ norecursedirs=dist build .tox .eggs addopts=--doctest-modules --flake8 --cov doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= + # workaround for skyfielders/python-skyfield#286 + ignore:object of type .* cannot be safely interpreted as an integer:::5 From c64fc03d6585d70596bc26ca933c01307db65a42 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Nov 2019 19:13:55 -0800 Subject: [PATCH 306/447] Fix spurious warnings in scotland --- calendra/tests/test_scotland.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/calendra/tests/test_scotland.py b/calendra/tests/test_scotland.py index fbf6bea4..00faf626 100644 --- a/calendra/tests/test_scotland.py +++ b/calendra/tests/test_scotland.py @@ -1,7 +1,8 @@ from datetime import date from unittest import TestCase, skipIf import sys -import warnings + +import pytest from ..tests import GenericCalendarTest from ..europe import ( @@ -16,6 +17,9 @@ PY2 = sys.version_info[0] == 2 +pytestmark = pytest.mark.filterwarnings('ignore:::calendra.europe.scotland') + + class GoodFridayTestMixin(object): def test_good_friday(self): holidays = self.cal.holidays_set(2018) @@ -279,17 +283,8 @@ class ScotlandTest(GenericCalendarTest): # at each call of constructor ; skipping if we're in a Python2 env. @skipIf(PY2, "Python 2 warnings unsupported") def test_init_warning(self): - warnings.simplefilter("always") - with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - # Trigger a warning. + with pytest.warns(UserWarning, match="experimental"): self.cal_class() - # Verify some things - assert len(w) == 1 - assert issubclass(w[-1].category, UserWarning) - assert "experimental" in str(w[-1].message) - # Back to normal filtering - warnings.simplefilter("ignore") def test_year_2018(self): holidays = self.cal.holidays_set(2018) From 40e4ec31125340a3792b42697387e99a6b86f5ae Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Nov 2019 19:18:56 -0800 Subject: [PATCH 307/447] Remove skip on Python 2, apparently not an issue. --- calendra/tests/test_scotland.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/calendra/tests/test_scotland.py b/calendra/tests/test_scotland.py index 00faf626..a98a842f 100644 --- a/calendra/tests/test_scotland.py +++ b/calendra/tests/test_scotland.py @@ -1,6 +1,5 @@ from datetime import date -from unittest import TestCase, skipIf -import sys +from unittest import TestCase import pytest @@ -14,9 +13,6 @@ ) -PY2 = sys.version_info[0] == 2 - - pytestmark = pytest.mark.filterwarnings('ignore:::calendra.europe.scotland') @@ -279,9 +275,6 @@ class ScotlandTest(GenericCalendarTest): """ cal_class = Scotland - # For some reason, the Python 2 warnings module doesn't trigger a warning - # at each call of constructor ; skipping if we're in a Python2 env. - @skipIf(PY2, "Python 2 warnings unsupported") def test_init_warning(self): with pytest.warns(UserWarning, match="experimental"): self.cal_class() From 307efeac45ddc0632eacca554b48515e5dd57882 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Nov 2019 19:24:01 -0800 Subject: [PATCH 308/447] Rely on pytest to trap the warning. Elegance begets simplicity. --- calendra/tests/test_usa.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/calendra/tests/test_usa.py b/calendra/tests/test_usa.py index 595772e1..b9529965 100644 --- a/calendra/tests/test_usa.py +++ b/calendra/tests/test_usa.py @@ -2,7 +2,8 @@ from unittest import skip, skipIf from datetime import date import sys -import warnings + +import pytest from . import GenericCalendarTest from ..usa import ( @@ -679,16 +680,12 @@ class FloridaLegalTest(IncludeMardiGras, ElectionDayEveryYear, @skipIf(PY2, "Python 2 warnings unsupported") def test_init_warning(self): - warnings.simplefilter("always") - with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - # Trigger a warning. + msg = ( + "Florida's laws separate the definitions between " + "paid versus legal holidays." + ) + with pytest.warns(UserWarning, match=msg): self.cal_class() - # Verify some things - assert len(w) == 1 - assert issubclass(w[-1].category, UserWarning) - assert "Florida's laws separate the definitions between paid versus legal holidays." in str(w[-1].message) # noqa - warnings.simplefilter("ignore") def test_specific_lincoln_birthday(self): holidays = self.cal.holidays_set(2014) From eebc61f2a21e3aaee3d7c0b6021a3a64a6183d3a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Nov 2019 19:25:21 -0800 Subject: [PATCH 309/447] Remove skip on Python 2, apparently not an issue. --- calendra/tests/test_usa.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/calendra/tests/test_usa.py b/calendra/tests/test_usa.py index b9529965..d775fc1e 100644 --- a/calendra/tests/test_usa.py +++ b/calendra/tests/test_usa.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from unittest import skip, skipIf +from unittest import skip from datetime import date -import sys import pytest @@ -27,9 +26,6 @@ ) -PY2 = sys.version_info[0] == 2 - - class UnitedStatesTest(GenericCalendarTest): cal_class = UnitedStates @@ -678,7 +674,6 @@ class FloridaLegalTest(IncludeMardiGras, ElectionDayEveryYear, """ cal_class = FloridaLegal - @skipIf(PY2, "Python 2 warnings unsupported") def test_init_warning(self): msg = ( "Florida's laws separate the definitions between " From 757b121d940f0daf7fbe4f494b47cb1b0ed6e0c2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 29 Nov 2019 10:31:33 -0500 Subject: [PATCH 310/447] Rename 'build-docs' to simply 'docs' (matching more popular convention). --- skeleton.md | 4 ++-- tox.ini | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/skeleton.md b/skeleton.md index 52b97f09..ee26e14b 100644 --- a/skeleton.md +++ b/skeleton.md @@ -85,7 +85,7 @@ The skeleton assumes the developer has [tox](https://pypi.org/project/tox) insta Other environments (invoked with `tox -e {name}`) supplied include: - - a `build-docs` environment to build the documentation + - a `docs` environment to build the documentation - a `release` environment to publish the package to PyPI A pytest.ini is included to define common options around running tests. In particular: @@ -122,7 +122,7 @@ TWINE_PASSWORD={token} travis env copy TWINE_PASSWORD ## Building Documentation -Documentation is automatically built by [Read the Docs](https://readthedocs.org) when the project is registered with it, by way of the .readthedocs.yml file. To test the docs build manually, a tox env may be invoked as `tox -e build-docs`. Both techniques rely on the dependencies declared in `setup.cfg/options.extras_require.docs`. +Documentation is automatically built by [Read the Docs](https://readthedocs.org) when the project is registered with it, by way of the .readthedocs.yml file. To test the docs build manually, a tox env may be invoked as `tox -e docs`. Both techniques rely on the dependencies declared in `setup.cfg/options.extras_require.docs`. In addition to building the sphinx docs scaffolded in `docs/`, the docs build a `history.html` file that first injects release dates and hyperlinks into the CHANGES.rst before incorporating it as history in the docs. diff --git a/tox.ini b/tox.ini index d86e4ad9..889af7a4 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ commands = usedevelop = True extras = testing -[testenv:build-docs] +[testenv:docs] extras = docs testing From 9ef4b6e60389a8f39cc04e466d12f42861c26472 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 30 Nov 2019 21:08:31 -0500 Subject: [PATCH 311/447] Prefer 'path' to 'path.py' --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 889af7a4..fa2a72f4 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ skip_install = True deps = pep517>=0.5 twine[keyring]>=1.13 - path.py + path passenv = TWINE_PASSWORD setenv = From 266a9c112ce2fb38365a13f57122fa73754555ce Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 8 Dec 2019 12:25:04 -0500 Subject: [PATCH 312/447] Cover Python 3.8 in Windows tests --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index bfd57529..6a1c99a9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ environment: matrix: - PYTHON: "C:\\Python36-x64" - - PYTHON: "C:\\Python37-x64" + - PYTHON: "C:\\Python38-x64" install: # symlink python from a directory with a space From 76786bdd11eac589bdaeb9a8cea5dcacaf613225 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 20 Dec 2019 23:49:53 -0500 Subject: [PATCH 313/447] Update black in pre-commit and add blacken-docs. --- .pre-commit-config.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e16c59ac..fe46b8c5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,10 @@ repos: - repo: https://github.com/psf/black - rev: 19.3b0 + rev: 19.10b0 hooks: - id: black + +- repo: https://github.com/asottile/blacken-docs + rev: v1.4.0 + hooks: + - id: blacken-docs From 051ce14211226aa34e11dc963cef508dd8ccdc53 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 4 Jan 2020 16:39:37 -0500 Subject: [PATCH 314/447] Test and release using Azure Pipelines --- .travis.yml | 9 ------ README.rst | 3 ++ azure-pipelines.yml | 71 +++++++++++++++++++++++++++++++++++++++++++++ skeleton.md | 34 ++++++++++++++++++---- 4 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 azure-pipelines.yml diff --git a/.travis.yml b/.travis.yml index 45cbcf94..17e45a67 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,15 +5,6 @@ python: - 3.6 - &latest_py3 3.8 -jobs: - fast_finish: true - include: - - stage: deploy - if: tag IS present - python: *latest_py3 - before_script: skip - script: tox -e release - cache: pip install: diff --git a/README.rst b/README.rst index 50eba567..a234ec9b 100644 --- a/README.rst +++ b/README.rst @@ -3,6 +3,9 @@ .. image:: https://img.shields.io/pypi/pyversions/skeleton.svg +.. image:: https://dev.azure.com/jaraco/skeleton/_apis/build/status/jaraco.skeleton?branchName=master + :target: https://dev.azure.com/jaraco/skeleton/_build/latest?definitionId=1&branchName=master + .. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg :target: https://travis-ci.org/jaraco/skeleton diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..d461bd00 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,71 @@ +# Create the project in Azure with: +# az project create --name $name --organization https://dev.azure.com/$org/ --visibility public +# then configure the pipelines (through web UI) + +trigger: + branches: + include: + - '*' + tags: + include: + - '*' + +pool: + vmimage: 'Ubuntu-18.04' + +variables: +- group: Azure secrets + +stages: +- stage: Test + jobs: + + - job: 'Test' + strategy: + matrix: + Python36: + python.version: '3.6' + Python38: + python.version: '3.8' + maxParallel: 4 + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '$(python.version)' + architecture: 'x64' + + - script: python -m pip install tox + displayName: 'Install tox' + + - script: | + tox -- --junit-xml=test-results.xml + displayName: 'run tests' + + - task: PublishTestResults@2 + inputs: + testResultsFiles: '**/test-results.xml' + testRunTitle: 'Python $(python.version)' + condition: succeededOrFailed() + +- stage: Publish + dependsOn: Test + jobs: + - job: 'Publish' + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.8' + architecture: 'x64' + + - script: python -m pip install tox + displayName: 'Install tox' + + - script: | + tox -e release + env: + TWINE_PASSWORD: $(PyPI-token) + displayName: 'publish to PyPI' + + condition: contains(variables['Build.SourceBranch'], 'tags') diff --git a/skeleton.md b/skeleton.md index ee26e14b..7e9955c9 100644 --- a/skeleton.md +++ b/skeleton.md @@ -103,23 +103,47 @@ Relies a .flake8 file to correct some default behaviors: ## Continuous Integration -The project is pre-configured to run tests in [Travis-CI](https://travis-ci.org) (.travis.yml). Any new project must be enabled either through their web site or with the `travis enable` command. +The project is pre-configured to run tests through multiple CI providers. + +### Azure Pipelines + +[Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/) are the preferred provider as they provide free, fast, multi-platform services. See azure-pipelines.yml for more details. + +Features include: + +- test against multiple Python versions +- run on Ubuntu Bionic + +### Travis CI + +[Travis-CI](https://travis-ci.org) is configured through .travis.yml. Any new project must be enabled either through their web site or with the `travis enable` command. Features include: -- test against Python 2 and 3 +- test against 3 - run on Ubuntu Xenial - correct for broken IPv6 -Also provided is a minimal template for running under Appveyor (Windows). +### Appveyor + +A minimal template for running under Appveyor (Windows) is provided. ### Continuous Deployments -In addition to running tests, an additional deploy stage is configured to automatically release tagged commits to PyPI using [API tokens](https://pypi.org/help/#apitoken). The release process expects an authorized token to be configured with Travis as the TWINE_PASSWORD environment variable. After the Travis project is created, configure the token through the web UI or with a command like the following (bash syntax): +In addition to running tests, an additional deploy stage is configured to automatically release tagged commits to PyPI using [API tokens](https://pypi.org/help/#apitoken). The release process expects an authorized token to be configured with Azure as the `Azure secrets` variable group. This variable group needs to be created only once per organization. For example: ``` -TWINE_PASSWORD={token} travis env copy TWINE_PASSWORD +# create a resource group if none exists +az group create --name main --location eastus2 +# create the vault (try different names until something works) +az keyvault create --name secrets007 --resource-group main +# create the secret +az keyvault secret set --vault-name secrets007 --name PyPI-token --value $token ``` +Then, in the web UI for the project's Pipelines Library, create the `Azure secrets` variable group referencing the key vault name. + +For more details, see [this blog entry](https://blog.jaraco.com/configuring-azure-pipelines-with-secets/). + ## Building Documentation Documentation is automatically built by [Read the Docs](https://readthedocs.org) when the project is registered with it, by way of the .readthedocs.yml file. To test the docs build manually, a tox env may be invoked as `tox -e docs`. Both techniques rely on the dependencies declared in `setup.cfg/options.extras_require.docs`. From b07b27332dcefc9ae9ad0a4d35ca4f39fa358233 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 11 Jan 2020 17:18:12 -0500 Subject: [PATCH 315/447] Correct guidance on project creation. --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d461bd00..3e80bf44 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,5 +1,5 @@ # Create the project in Azure with: -# az project create --name $name --organization https://dev.azure.com/$org/ --visibility public +# az devops project create --name $name --organization https://dev.azure.com/$org/ --visibility public # then configure the pipelines (through web UI) trigger: From 0bf3d43d97d5466fddac708e7ca38ba95281c6e7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 18 Jan 2020 13:36:43 -0500 Subject: [PATCH 316/447] Rely on setuptools_scm 3.4 and setuptools 42. Now setup.py is optional. Remove setuptools from test environment. --- pyproject.toml | 4 +++- setup.cfg | 2 +- setup.py | 2 +- tox.ini | 1 - 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3afc8c33..74cff744 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,8 @@ [build-system] -requires = ["setuptools>=34.4", "wheel", "setuptools_scm>=1.15"] +requires = ["setuptools>=42", "wheel", "setuptools_scm>=3.4"] build-backend = "setuptools.build_meta" [tool.black] skip-string-normalization = true + +[tool.setuptools_scm] diff --git a/setup.cfg b/setup.cfg index 2c0f7b1f..bc7a9d5d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,7 +20,7 @@ packages = find: include_package_data = true python_requires = >=3.6 install_requires = -setup_requires = setuptools_scm >= 1.15.0 +setup_requires = setuptools_scm >= 3.4 [options.extras_require] testing = diff --git a/setup.py b/setup.py index 827e955f..bac24a43 100644 --- a/setup.py +++ b/setup.py @@ -3,4 +3,4 @@ import setuptools if __name__ == "__main__": - setuptools.setup(use_scm_version=True) + setuptools.setup() diff --git a/tox.ini b/tox.ini index fa2a72f4..fb98930e 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,6 @@ requires = [testenv] deps = - setuptools>=31.0.1 pip_version = pip commands = pytest {posargs} From d9934dc8f5dafb93ca565eb0f8ec9e0c245b8d68 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 18 Jan 2020 20:50:32 +0200 Subject: [PATCH 317/447] Spelling and capitalisation (#8) Co-authored-by: Jason R. Coombs --- skeleton.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/skeleton.md b/skeleton.md index 7e9955c9..439df2f0 100644 --- a/skeleton.md +++ b/skeleton.md @@ -2,9 +2,9 @@ This project is merged with [skeleton](https://github.com/jaraco/skeleton). What is skeleton? It's the scaffolding of a Python project jaraco [introduced in his blog](https://blog.jaraco.com/a-project-skeleton-for-python-projects/). It seeks to provide a means to re-use techniques and inherit advances when managing projects for distribution. -## An SCM Managed Approach +## An SCM-Managed Approach -While maintaining dozens of projects in PyPI, jaraco derives best practices for project distribution and publishes them in the [skeleton repo](https://github.com/jaraco/skeleton), a git repo capturing the evolution and culmination of these best practices. +While maintaining dozens of projects in PyPI, jaraco derives best practices for project distribution and publishes them in the [skeleton repo](https://github.com/jaraco/skeleton), a Git repo capturing the evolution and culmination of these best practices. It's intended to be used by a new or existing project to adopt these practices and honed and proven techniques. Adopters are encouraged to use the project directly and maintain a small deviation from the technique, make their own fork for more substantial changes unique to their environment or preferences, or simply adopt the skeleton once and abandon it thereafter. @@ -38,7 +38,7 @@ The `--allow-unrelated-histories` is necessary because the history from the skel ## Updating -Whenever a change is needed or desired for the general technique for packaging, it can be made in the skeleton project and then merged into each of the derived projects as needed, recommended before each release. As a result, features and best practices for packaging are centrally maintained and readily trickle into a whole suite of packages. This technique lowers the amount of tedious work necessary to create or maintain a project, and coupled with other techniques like continuous integration and deployment, lowers the cost of creating and maintaining refined Python projects to just a few, familiar git operations. +Whenever a change is needed or desired for the general technique for packaging, it can be made in the skeleton project and then merged into each of the derived projects as needed, recommended before each release. As a result, features and best practices for packaging are centrally maintained and readily trickle into a whole suite of packages. This technique lowers the amount of tedious work necessary to create or maintain a project, and coupled with other techniques like continuous integration and deployment, lowers the cost of creating and maintaining refined Python projects to just a few, familiar Git operations. Thereafter, the target project can make whatever customizations it deems relevant to the scaffolding. The project may even at some point decide that the divergence is too great to merit renewed merging with the original skeleton. This approach applies maximal guidance while creating minimal constraints. @@ -46,16 +46,16 @@ Thereafter, the target project can make whatever customizations it deems relevan The features/techniques employed by the skeleton include: -- PEP 517/518 based build relying on setuptools as the build tool -- setuptools declarative configuration using setup.cfg +- PEP 517/518-based build relying on Setuptools as the build tool +- Setuptools declarative configuration using setup.cfg - tox for running tests -- A README.rst as reStructuredText with some popular badges, but with readthedocs and appveyor badges commented out +- A README.rst as reStructuredText with some popular badges, but with Read the Docs and AppVeyor badges commented out - A CHANGES.rst file intended for publishing release notes about the project -- Use of [black](https://black.readthedocs.io/en/stable/) for code formatting (disabled on unsupported Python 3.5 and earlier) +- Use of [Black](https://black.readthedocs.io/en/stable/) for code formatting (disabled on unsupported Python 3.5 and earlier) ## Packaging Conventions -A pyproject.toml is included to enable PEP 517 and PEP 518 compatibility and declares the requirements necessary to build the project on setuptools (a minimum version compatible with setup.cfg declarative config). +A pyproject.toml is included to enable PEP 517 and PEP 518 compatibility and declares the requirements necessary to build the project on Setuptools (a minimum version compatible with setup.cfg declarative config). The setup.cfg file implements the following features: @@ -92,14 +92,14 @@ A pytest.ini is included to define common options around running tests. In parti - rely on default test discovery in the current directory - avoid recursing into common directories not containing tests -- run doctests on modules and invoke flake8 tests -- in doctests, allow unicode literals and regular literals to match, allowing for doctests to run on Python 2 and 3. Also enable ELLIPSES, a default that would be undone by supplying the prior option. +- run doctests on modules and invoke Flake8 tests +- in doctests, allow Unicode literals and regular literals to match, allowing for doctests to run on Python 2 and 3. Also enable ELLIPSES, a default that would be undone by supplying the prior option. - filters out known warnings caused by libraries/functionality included by the skeleton -Relies a .flake8 file to correct some default behaviors: +Relies on a .flake8 file to correct some default behaviors: - disable mutually incompatible rules W503 and W504 -- support for black format +- support for Black format ## Continuous Integration @@ -116,10 +116,10 @@ Features include: ### Travis CI -[Travis-CI](https://travis-ci.org) is configured through .travis.yml. Any new project must be enabled either through their web site or with the `travis enable` command. +[Travis CI](https://travis-ci.org) is configured through .travis.yml. Any new project must be enabled either through their web site or with the `travis enable` command. Features include: -- test against 3 +- test against Python 3 - run on Ubuntu Xenial - correct for broken IPv6 @@ -148,7 +148,7 @@ For more details, see [this blog entry](https://blog.jaraco.com/configuring-azur Documentation is automatically built by [Read the Docs](https://readthedocs.org) when the project is registered with it, by way of the .readthedocs.yml file. To test the docs build manually, a tox env may be invoked as `tox -e docs`. Both techniques rely on the dependencies declared in `setup.cfg/options.extras_require.docs`. -In addition to building the sphinx docs scaffolded in `docs/`, the docs build a `history.html` file that first injects release dates and hyperlinks into the CHANGES.rst before incorporating it as history in the docs. +In addition to building the Sphinx docs scaffolded in `docs/`, the docs build a `history.html` file that first injects release dates and hyperlinks into the CHANGES.rst before incorporating it as history in the docs. ## Cutting releases From 7558cfe2eb2f1ffe3676905e9871466cbc9da24f Mon Sep 17 00:00:00 2001 From: johnthagen Date: Tue, 14 Jan 2020 07:53:19 -0500 Subject: [PATCH 318/447] Line wrap LICENSE file --- LICENSE | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 5e795a61..353924be 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,19 @@ Copyright Jason R. Coombs -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: +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 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. +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. From 479ac2149d872757160732bc977977ee0192ac51 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 10:51:33 -0500 Subject: [PATCH 319/447] Finish dropping support for Python 2 (I hope). --- setup.cfg | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index bc7a9d5d..3e621072 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[bdist_wheel] -universal = 1 - [metadata] license_file = LICENSE name = skeleton @@ -14,6 +11,7 @@ classifiers = Intended Audience :: Developers License :: OSI Approved :: MIT License Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only [options] packages = find: From 7ea8d688deae595ba345cb016d4a926d6688e587 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 18:54:45 -0500 Subject: [PATCH 320/447] Remove warning filter now that issue is fixed. --- pytest.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/pytest.ini b/pytest.ini index 173f2b24..e7ee0faf 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,5 +3,3 @@ norecursedirs=dist build .tox .eggs addopts=--doctest-modules --flake8 --cov doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= - # workaround for skyfielders/python-skyfield#286 - ignore:object of type .* cannot be safely interpreted as an integer:::5 From d0be2c80476ed2acc6769b6b494e7a264cf0140a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 19 Jan 2020 22:45:38 -0500 Subject: [PATCH 321/447] Remove explicit super() references --- calendra/america/brazil.py | 2 +- calendra/core.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/calendra/america/brazil.py b/calendra/america/brazil.py index 019594d0..efe0947d 100644 --- a/calendra/america/brazil.py +++ b/calendra/america/brazil.py @@ -630,7 +630,7 @@ def get_variable_days(self, year): ] return ( - super(Brazil, self).get_variable_days(year) + super().get_variable_days(year) + non_fixed_holidays + non_working_days ) diff --git a/calendra/core.py b/calendra/core.py index c07d675b..141cf563 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -68,7 +68,7 @@ class Holiday(date): """ def __new__(cls, date, *args, **kwargs): - return super(Holiday, cls).__new__( + return super().__new__( cls, date.year, date.month, date.day) def __init__(self, date, name='Holiday', **kwargs): @@ -90,7 +90,7 @@ def __iter__(self): return iter(tp) def replace(self, *args, **kwargs): - replaced = super(Holiday, self).replace(*args, **kwargs) + replaced = super().replace(*args, **kwargs) vars(replaced).update(vars(self)) return replaced From 29c497286e9f8e835c68b7b4ddb9ff3635a273dc Mon Sep 17 00:00:00 2001 From: layday <31134424+layday@users.noreply.github.com> Date: Thu, 23 Jan 2020 03:48:38 +0200 Subject: [PATCH 322/447] Require toml extra for setuptools_scm (#12) * Require toml extra for setuptools_scm setuptools_scm does not know to invoke itself if it can't read pyproject.toml. This broke sdist installs for projects deriving from skeleton: $ python -m pip install zipp --no-binary zipp Collecting zipp [...] Successfully installed zipp-0.0.0 Note the version number defaulting to '0.0.0'. Building locally only works because pep517, the build tool, depends on toml which it exposes to the build environment. * Require setuptools_scm 3.4.1 at a minimum A bare [tool.setuptools_scm] does not work in 3.4.0. * fixup! Require toml extra for setuptools_scm * fixup! Require setuptools_scm 3.4.1 at a minimum --- pyproject.toml | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 74cff744..6ee7df23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=42", "wheel", "setuptools_scm>=3.4"] +requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4.1"] build-backend = "setuptools.build_meta" [tool.black] diff --git a/setup.cfg b/setup.cfg index 3e621072..c20fa103 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ packages = find: include_package_data = true python_requires = >=3.6 install_requires = -setup_requires = setuptools_scm >= 3.4 +setup_requires = setuptools_scm[toml] >= 3.4.1 [options.extras_require] testing = From 9a7d846ae8775e5c49cc9933fa897cc761c3b20b Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 18 Jan 2020 20:53:39 +0200 Subject: [PATCH 323/447] Fix AppVeyor typo --- skeleton.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skeleton.md b/skeleton.md index 439df2f0..340b65cf 100644 --- a/skeleton.md +++ b/skeleton.md @@ -123,9 +123,9 @@ Features include: - run on Ubuntu Xenial - correct for broken IPv6 -### Appveyor +### AppVeyor -A minimal template for running under Appveyor (Windows) is provided. +A minimal template for running under AppVeyor (Windows) is provided. ### Continuous Deployments From 814b7f74d07e961ac94239d6e5b421a6d9d963dc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 22 Jan 2020 21:11:28 -0500 Subject: [PATCH 324/447] Update changelog. --- CHANGES.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3dca2c10..17d7e2ae 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,8 @@ +v6.1.1 +------ + +Fix version inference when installed from sdist. + v6.1.0 ------ From 0aa6c5c191ad22bbc4ad30d0880e531d4e66c0b3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 23 Jan 2020 22:51:29 +0200 Subject: [PATCH 325/447] Link badge to PyPI rather than static image And DRY the link --- README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a234ec9b..4c7fd554 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,10 @@ .. image:: https://img.shields.io/pypi/v/skeleton.svg - :target: https://pypi.org/project/skeleton + :target: `PyPI link`_ .. image:: https://img.shields.io/pypi/pyversions/skeleton.svg + :target: `PyPI link`_ + +.. _PyPI link: https://pypi.org/project/skeleton .. image:: https://dev.azure.com/jaraco/skeleton/_apis/build/status/jaraco.skeleton?branchName=master :target: https://dev.azure.com/jaraco/skeleton/_build/latest?definitionId=1&branchName=master From 0b681b040f3e13793c61c1dfe6e510d9e3a8870a Mon Sep 17 00:00:00 2001 From: Vincent Fazio Date: Tue, 4 Feb 2020 14:51:03 -0600 Subject: [PATCH 326/447] setup.cfg: let python-tag mirror python_requires In order to generate a wheel in accordance with PEP 425 to restrict the minimum required version of Python (3.6), the `python-tag` bdist_wheel option needs to be specified so the wheel gets tagged properly. Before: zipp-x.x.x-py3-none-any.whl After: zipp-x.x.x-py36-none-any.whl Signed-off-by: Vincent Fazio --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index c20fa103..f2643ea2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,6 +13,9 @@ classifiers = Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only +[bdist_wheel] +python-tag=py36 + [options] packages = find: include_package_data = true From eb00cd0636bc3e4cef9217b2c5eccdcaebbe5d65 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 9 Feb 2020 09:29:18 -0500 Subject: [PATCH 327/447] Normalize whitespace --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f2643ea2..3436e6b0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,7 +14,7 @@ classifiers = Programming Language :: Python :: 3 :: Only [bdist_wheel] -python-tag=py36 +python-tag = py36 [options] packages = find: From 4f845452dd0f7e95b7959b8e0ea50374c73b7920 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 17 Feb 2020 11:23:25 -0500 Subject: [PATCH 328/447] Revert "setup.cfg: let python-tag mirror python_requires" This reverts commit 0b681b040f3e13793c61c1dfe6e510d9e3a8870a. Ref jaraco/zipp#42 --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 3436e6b0..c20fa103 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,9 +13,6 @@ classifiers = Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only -[bdist_wheel] -python-tag = py36 - [options] packages = find: include_package_data = true From f374f66c28a2e219fcdcbd61c36255792af96f86 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 23 Feb 2020 09:01:05 -0500 Subject: [PATCH 329/447] Rely on importlib.metadata for __version__. Fixes #14. --- calendra/__init__.py | 7 +++++-- setup.cfg | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/calendra/__init__.py b/calendra/__init__.py index 66141d90..2081185e 100644 --- a/calendra/__init__.py +++ b/calendra/__init__.py @@ -1,5 +1,8 @@ -import pkg_resources +try: + import importlib.metadata as metadata +except ImportError: + import importlib_metadata as metadata #: Module version, as defined in PEP-0396. -__version__ = pkg_resources.get_distribution(__package__ or __name__).version +__version__ = metadata.version(__package__) diff --git a/setup.cfg b/setup.cfg index d8fcbf48..330a859e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,7 @@ install_requires = skyfield skyfield-data pyluach + importlib_metadata; python_version < "3.8" setup_requires = setuptools_scm[toml] >= 3.4.1 [options.extras_require] From 6f45329ca0cbec224ef759afc75b7a7e8d2d873b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 23 Feb 2020 09:02:10 -0500 Subject: [PATCH 330/447] Update changelog. Ref #14. --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 17d7e2ae..661b6cfa 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,9 @@ +v6.1.2 +------ + +#14: Replaced implicit dependency on setuptools with explicit +dependency on importlib.metadata. + v6.1.1 ------ From 8fbd841d17a029232577d71587c02aa429874287 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 28 Feb 2020 17:36:22 -0600 Subject: [PATCH 331/447] Update to bionic for Travis. Correct comment about IPv6 workaround. --- .travis.yml | 4 ++-- skeleton.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17e45a67..923377f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -dist: xenial +dist: bionic language: python python: @@ -11,7 +11,7 @@ install: - pip install tox tox-venv before_script: - # Disable IPv6. Ref travis-ci/travis-ci#8361 + # Enable IPv6. Ref travis-ci/travis-ci#8361 - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'; fi diff --git a/skeleton.md b/skeleton.md index 340b65cf..4544158f 100644 --- a/skeleton.md +++ b/skeleton.md @@ -120,7 +120,7 @@ Features include: Features include: - test against Python 3 -- run on Ubuntu Xenial +- run on Ubuntu Bionic - correct for broken IPv6 ### AppVeyor From b4dd44cc7c8da44bcc8f2674d2a5d764091f63c6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 15 Mar 2020 15:05:37 -0400 Subject: [PATCH 332/447] Suppress warnings in pytest-flake8, pytest-black, and pytest-checkdocs. --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest.ini b/pytest.ini index 7b9b714f..60d9bf76 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,3 +3,5 @@ norecursedirs=dist build .tox .eggs addopts=--doctest-modules --flake8 --black --cov doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= + # https://github.com/pytest-dev/pytest/issues/6928 + ignore:direct construction of .*Item has been deprecated:DeprecationWarning From 2c2fee097e1898a8c00b4434f74063302aeb96e9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 27 Mar 2020 09:22:01 -0400 Subject: [PATCH 333/447] Prefer pytest-black to pytest-black-multipy --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index c20fa103..3f887a64 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,7 +26,7 @@ testing = pytest >= 3.5, !=3.7.3 pytest-checkdocs >= 1.2.3 pytest-flake8 - pytest-black-multipy + pytest-black >= 0.3.7 pytest-cov # local From 045e6ad88af135f85365248812cbe5ddb3faf355 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 5 Apr 2020 14:51:43 -0400 Subject: [PATCH 334/447] Test against Windows and Mac --- azure-pipelines.yml | 80 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..25e638b5 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,80 @@ +# Create the project in Azure with: +# az devops project create --name $name --organization https://dev.azure.com/$org/ --visibility public +# then configure the pipelines (through web UI) + +trigger: + branches: + include: + - '*' + tags: + include: + - '*' + +pool: + vmImage: $(pool_vm_image) + +variables: +- group: Azure secrets + +stages: +- stage: Test + jobs: + + - job: 'Test' + strategy: + matrix: + Bionic Python 3.6: + python.version: '3.6' + pool_vm_image: Ubuntu-18.04 + Bionic Python 3.8: + python.version: '3.8' + pool_vm_image: Ubuntu-18.04 + Windows: + python.version: '3.8' + pool_vm_image: vs2017-win2016 + MacOS: + python.version: '3.8' + pool_vm_image: macos-10.15 + + maxParallel: 4 + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '$(python.version)' + architecture: 'x64' + + - script: python -m pip install tox + displayName: 'Install tox' + + - script: | + tox -- --junit-xml=test-results.xml + displayName: 'run tests' + + - task: PublishTestResults@2 + inputs: + testResultsFiles: '**/test-results.xml' + testRunTitle: 'Python $(python.version)' + condition: succeededOrFailed() + +- stage: Publish + dependsOn: Test + jobs: + - job: 'Publish' + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.8' + architecture: 'x64' + + - script: python -m pip install tox + displayName: 'Install tox' + + - script: | + tox -e release + env: + TWINE_PASSWORD: $(PyPI-token) + displayName: 'publish to PyPI' + + condition: contains(variables['Build.SourceBranch'], 'tags') From bb96dc3fcd738e3202423a0ed09ea9f5dd4ce50b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 25 Apr 2020 09:54:59 -0400 Subject: [PATCH 335/447] Define a default pool_vm_image --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 25e638b5..fd432962 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,6 +15,8 @@ pool: variables: - group: Azure secrets +- name: pool_vm_image + value: Ubuntu-18.04 stages: - stage: Test @@ -25,10 +27,8 @@ stages: matrix: Bionic Python 3.6: python.version: '3.6' - pool_vm_image: Ubuntu-18.04 Bionic Python 3.8: python.version: '3.8' - pool_vm_image: Ubuntu-18.04 Windows: python.version: '3.8' pool_vm_image: vs2017-win2016 From f66d87899937361bea35f437ba293774f0375ca2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 27 Apr 2020 18:49:51 -0400 Subject: [PATCH 336/447] Remove tox-venv and tox-pip-version. Tox-venv is discouraged (https://github.com/tox-dev/tox-venv/issues/48#issuecomment-620227405) and tox-pip-version was only there to support tox-venv. venv is dead; long live virtualenv. --- tox.ini | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tox.ini b/tox.ini index fb98930e..ba5857a3 100644 --- a/tox.ini +++ b/tox.ini @@ -3,15 +3,10 @@ envlist = python minversion = 3.2 # https://github.com/jaraco/skeleton/issues/6 tox_pip_extensions_ext_venv_update = true -# Ensure that a late version of pip is used even on tox-venv. -requires = - tox-pip-version>=0.0.6 - tox-venv [testenv] deps = -pip_version = pip commands = pytest {posargs} usedevelop = True From 0df8947fc97f0f0fab6e680d20d8affe5838aec3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 May 2020 23:15:53 -0400 Subject: [PATCH 337/447] Remove more references to tox-venv --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 923377f6..37fa499d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ python: cache: pip install: -- pip install tox tox-venv +- pip install tox before_script: # Enable IPv6. Ref travis-ci/travis-ci#8361 diff --git a/appveyor.yml b/appveyor.yml index 6a1c99a9..c6f46e4f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,7 +18,7 @@ cache: - '%LOCALAPPDATA%\pip\Cache' test_script: - - "python -m pip install -U tox tox-venv virtualenv" + - "python -m pip install -U tox virtualenv" - "tox" version: '{build}' From 649bc7934a13b78eb1283cad9919b9f26c5426e9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 2 May 2020 07:25:43 -0400 Subject: [PATCH 338/447] Add workaround for warning emitted when junitxml is used. Ref pytest-dev/pytest#6178. --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest.ini b/pytest.ini index 60d9bf76..62c0f365 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,6 +2,8 @@ norecursedirs=dist build .tox .eggs addopts=--doctest-modules --flake8 --black --cov doctest_optionflags=ALLOW_UNICODE ELLIPSIS +# workaround for warning pytest-dev/pytest#6178 +junit_family=xunit2 filterwarnings= # https://github.com/pytest-dev/pytest/issues/6928 ignore:direct construction of .*Item has been deprecated:DeprecationWarning From 7455f2f25310b2d778a648e45d32033ccc790946 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 3 May 2020 15:31:25 -0400 Subject: [PATCH 339/447] Include mypy for type checking during tests. --- mypy.ini | 2 ++ pytest.ini | 2 +- setup.cfg | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..976ba029 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +ignore_missing_imports = True diff --git a/pytest.ini b/pytest.ini index 62c0f365..381b3271 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] norecursedirs=dist build .tox .eggs -addopts=--doctest-modules --flake8 --black --cov +addopts=--doctest-modules --flake8 --black --cov --mypy doctest_optionflags=ALLOW_UNICODE ELLIPSIS # workaround for warning pytest-dev/pytest#6178 junit_family=xunit2 diff --git a/setup.cfg b/setup.cfg index 3f887a64..e2dcebb1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ testing = pytest-flake8 pytest-black >= 0.3.7 pytest-cov + pytest-mypy # local From 172c52b03a77e70e92a1ee491b059464e938fde3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 22 May 2020 11:31:51 -0400 Subject: [PATCH 340/447] Ensure virtualenv is upgraded when installing tox. Fixes jaraco/path#188. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 37fa499d..eed7b0a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,8 @@ python: cache: pip install: -- pip install tox +# ensure virtualenv is upgraded to avoid issues like jaraco/path#188 +- pip install -U --upgrade-strategy=eager tox before_script: # Enable IPv6. Ref travis-ci/travis-ci#8361 From c764baf26c001ca5828fe99b8aa33aa55e9fd554 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 22 May 2020 23:17:49 -0400 Subject: [PATCH 341/447] Run tests on prereleases of Python on Windows. Fixes jaraco/skeleton#17. --- azure-pipelines.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fd432962..2be55e73 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -29,9 +29,12 @@ stages: python.version: '3.6' Bionic Python 3.8: python.version: '3.8' - Windows: + Windows Python 3.8: python.version: '3.8' pool_vm_image: vs2017-win2016 + Windows Python Prerelease: + python.version: '3.9' + pool_vm_image: vs2017-win2016 MacOS: python.version: '3.8' pool_vm_image: macos-10.15 @@ -39,10 +42,21 @@ stages: maxParallel: 4 steps: + - task: NuGetToolInstaller@1 + displayName: 'Install NuGet' + condition: eq(variables['pool_vm_image'], 'vs2017-win2016') + + - powershell: | + nuget install python -Prerelease -OutputDirectory "$(Build.BinariesDirectory)" -ExcludeVersion -NonInteractive + Write-Host "##vso[task.prependpath]$(Build.BinariesDirectory)\python\tools" + Write-Host "##vso[task.prependpath]$(Build.BinariesDirectory)\python\tools\Scripts" + condition: and(succeeded(), and(eq(variables['python.version'], '3.9'), eq(variables['pool_vm_image'], 'vs2017-win2016'))) + - task: UsePythonVersion@0 inputs: versionSpec: '$(python.version)' architecture: 'x64' + condition: and(succeeded(), ne(variables['python.version'], '3.9')) - script: python -m pip install tox displayName: 'Install tox' From fc0162baafbd984f245de359b5a64c2d997a0714 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 23 May 2020 11:52:32 -0400 Subject: [PATCH 342/447] Add workaround for python/mypy#8627. Fixes jaraco/skeleton#18. --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index e2dcebb1..0cf95493 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,6 +29,8 @@ testing = pytest-black >= 0.3.7 pytest-cov pytest-mypy + # workaround for python/mypy#8627 + mypy@git+https://github.com/python/mypy # local From 814eb669c2e5436d8ebce1be845fa99e0687317b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 23 May 2020 12:17:23 -0400 Subject: [PATCH 343/447] Add 'refresh.svg' demonstrating an example of refreshing a project with the latest skeleton. Ref #7. --- docs/refresh.svg | 193 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 docs/refresh.svg diff --git a/docs/refresh.svg b/docs/refresh.svg new file mode 100644 index 00000000..04e62f00 --- /dev/null +++ b/docs/refresh.svg @@ -0,0 +1,193 @@ + + + + + + + + + + + path master $ path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton From https://github.com/jaraco/skeleton * branch HEAD -> FETCH_HEADpath master $ path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH_ path master $ git diff ...FETCH_H path master $ git diff ...FETCH_HE path master $ git diff ...FETCH_HEA path master $ git diff ...FETCH_HEAD path master $ git diff ...FETCH_HEAD diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fd43296..2be55e7 100644--- a/azure-pipelines.yml+++ b/azure-pipelines.yml@@ -29,9 +29,12 @@ stages: python.version: '3.6' Bionic Python 3.8: python.version: '3.8'- Windows:+ Windows Python 3.8: pool_vm_image: vs2017-win2016+ Windows Python Prerelease:+ python.version: '3.9'+ pool_vm_image: vs2017-win2016 MacOS: pool_vm_image: macos-10.15@@ -39,10 +42,21 @@ stages: maxParallel: 4 steps:+ - task: NuGetToolInstaller@1+ displayName: 'Install NuGet'+ condition: eq(variables['pool_vm_image'], 'vs2017-win2016')++ - powershell: |+ nuget install python -Prerelease -OutputDi + nuget install python -Prerelease -OutputDirectory "$(Build.BinariesDirectory)" -ExcludeVersion -NonInteractive+ Write-Host "##vso[task.prependpath]$(Build.BinariesDirectory)\python\tools"+ Write-Host "##vso[task.prependpath]$(Build.BinariesDirectory)\python\tools\Scripts"+ condition: and(succeeded(), and(eq(variables['python.version'], '3.9'), eq(variables['pool_vm_image'], 'vs2017-win2016'))) - task: UsePythonVersion@0 inputs: versionSpec: '$(python.version)' architecture: 'x64'+ condition: and(succeeded(), ne(variables['python.version'], '3.9')) - script: python -m pip install tox displayName: 'Install tox'diff --git a/setup.cfg b/setup.cfgindex e2dcebb..0cf9549 100644--- a/setup.cfg+++ b/setup.cfg@@ -29,6 +29,8 @@ testing = pytest-black >= 0.3.7 pytest-cov pytest-mypy+ # workaround for python/mypy#8627: + mypy@git+https://github.com/python/mypy # local(END) path master $ git diff ...FETCH_HEAD path master $ git diff ...FETCH_HEAD path master $ git diff ...FETCH_HEAD path master $ git diff ...FETCH_HEAD path master $ git push path master $ git push path master $ git pull path master $ git pull path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton Auto-merging setup.cfgAuto-merging azure-pipelines.ymlMerge made by the 'recursive' strategy. azure-pipelines.yml | 16 +++++++++++++++- setup.cfg | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-)path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git push path master $ git push path master $ git push Enumerating objects: 10, done. Counting objects: 50% (5/10)Counting objects: 100% (10/10), done.Delta compression using up to 8 threadsCompressing objects: 100% (4/4), done.Writing objects: 100% (4/4), 512 bytes | 512.00 KiB/s, done.Total 4 (delta 3), reused 0 (delta 0), pack-reused 0remote: Resolving deltas: 100% (3/3), completed with 3 local objects. To https://github.com/jaraco/path ff4d395..bd18026 master -> masterpath master $ + \ No newline at end of file From 169dad91b8316a7138bec2f7b51db80cab0aadc4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 23 May 2020 22:40:39 -0400 Subject: [PATCH 344/447] Move workaround for python/mypy#8627 to tox.ini, as adding it to setup.cfg prevents releases to PyPI. Fixes jaraco/skeleton#19. --- setup.cfg | 2 -- tox.ini | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 0cf95493..e2dcebb1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,8 +29,6 @@ testing = pytest-black >= 0.3.7 pytest-cov pytest-mypy - # workaround for python/mypy#8627 - mypy@git+https://github.com/python/mypy # local diff --git a/tox.ini b/tox.ini index ba5857a3..97cc4261 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,8 @@ tox_pip_extensions_ext_venv_update = true [testenv] deps = + # workaround for python/mypy#8627 + mypy@git+https://github.com/python/mypy commands = pytest {posargs} usedevelop = True From a2fc27929fe7f7d6dc87700560d58abd08ddad46 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 26 Jul 2020 03:33:59 -0400 Subject: [PATCH 345/447] Remove workaround for python/mypy#8627. Ref jaraco/skeleton#18. --- tox.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tox.ini b/tox.ini index 97cc4261..ba5857a3 100644 --- a/tox.ini +++ b/tox.ini @@ -7,8 +7,6 @@ tox_pip_extensions_ext_venv_update = true [testenv] deps = - # workaround for python/mypy#8627 - mypy@git+https://github.com/python/mypy commands = pytest {posargs} usedevelop = True From e630a0c8f213e4013b57a695187755641f7537a3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 30 Aug 2020 14:30:41 -0400 Subject: [PATCH 346/447] Create Github releases when releasing the package. Fixes jaraco/skeleton#23. --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index ba5857a3..7347d95f 100644 --- a/tox.ini +++ b/tox.ini @@ -26,11 +26,14 @@ deps = pep517>=0.5 twine[keyring]>=1.13 path + jaraco.develop>=7.1 passenv = TWINE_PASSWORD + GITHUB_TOKEN setenv = TWINE_USERNAME = {env:TWINE_USERNAME:__token__} commands = python -c "import path; path.Path('dist').rmtree_p()" python -m pep517.build . python -m twine upload dist/* + python -m jaraco.develop.create-github-release From 8032e1d3fb114bb93736d5b6668adc4d44f6c00b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 30 Aug 2020 15:26:29 -0400 Subject: [PATCH 347/447] Moved refresh.svg to another branch. Reference the animation from the docs. Ref jaraco/skeleton#7. --- docs/refresh.svg | 193 ----------------------------------------------- skeleton.md | 4 + 2 files changed, 4 insertions(+), 193 deletions(-) delete mode 100644 docs/refresh.svg diff --git a/docs/refresh.svg b/docs/refresh.svg deleted file mode 100644 index 04e62f00..00000000 --- a/docs/refresh.svg +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - - - - - path master $ path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton From https://github.com/jaraco/skeleton * branch HEAD -> FETCH_HEADpath master $ path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git fetch gh://jaraco/skeleton path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH path master $ git diff ...FETCH_ path master $ git diff ...FETCH_H path master $ git diff ...FETCH_HE path master $ git diff ...FETCH_HEA path master $ git diff ...FETCH_HEAD path master $ git diff ...FETCH_HEAD diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fd43296..2be55e7 100644--- a/azure-pipelines.yml+++ b/azure-pipelines.yml@@ -29,9 +29,12 @@ stages: python.version: '3.6' Bionic Python 3.8: python.version: '3.8'- Windows:+ Windows Python 3.8: pool_vm_image: vs2017-win2016+ Windows Python Prerelease:+ python.version: '3.9'+ pool_vm_image: vs2017-win2016 MacOS: pool_vm_image: macos-10.15@@ -39,10 +42,21 @@ stages: maxParallel: 4 steps:+ - task: NuGetToolInstaller@1+ displayName: 'Install NuGet'+ condition: eq(variables['pool_vm_image'], 'vs2017-win2016')++ - powershell: |+ nuget install python -Prerelease -OutputDi + nuget install python -Prerelease -OutputDirectory "$(Build.BinariesDirectory)" -ExcludeVersion -NonInteractive+ Write-Host "##vso[task.prependpath]$(Build.BinariesDirectory)\python\tools"+ Write-Host "##vso[task.prependpath]$(Build.BinariesDirectory)\python\tools\Scripts"+ condition: and(succeeded(), and(eq(variables['python.version'], '3.9'), eq(variables['pool_vm_image'], 'vs2017-win2016'))) - task: UsePythonVersion@0 inputs: versionSpec: '$(python.version)' architecture: 'x64'+ condition: and(succeeded(), ne(variables['python.version'], '3.9')) - script: python -m pip install tox displayName: 'Install tox'diff --git a/setup.cfg b/setup.cfgindex e2dcebb..0cf9549 100644--- a/setup.cfg+++ b/setup.cfg@@ -29,6 +29,8 @@ testing = pytest-black >= 0.3.7 pytest-cov pytest-mypy+ # workaround for python/mypy#8627: + mypy@git+https://github.com/python/mypy # local(END) path master $ git diff ...FETCH_HEAD path master $ git diff ...FETCH_HEAD path master $ git diff ...FETCH_HEAD path master $ git diff ...FETCH_HEAD path master $ git push path master $ git push path master $ git pull path master $ git pull path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton Auto-merging setup.cfgAuto-merging azure-pipelines.ymlMerge made by the 'recursive' strategy. azure-pipelines.yml | 16 +++++++++++++++- setup.cfg | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-)path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git pull gh://jaraco/skeleton path master $ git push path master $ git push path master $ git push Enumerating objects: 10, done. Counting objects: 50% (5/10)Counting objects: 100% (10/10), done.Delta compression using up to 8 threadsCompressing objects: 100% (4/4), done.Writing objects: 100% (4/4), 512 bytes | 512.00 KiB/s, done.Total 4 (delta 3), reused 0 (delta 0), pack-reused 0remote: Resolving deltas: 100% (3/3), completed with 3 local objects. To https://github.com/jaraco/path ff4d395..bd18026 master -> masterpath master $ - \ No newline at end of file diff --git a/skeleton.md b/skeleton.md index 4544158f..17a94ed7 100644 --- a/skeleton.md +++ b/skeleton.md @@ -40,6 +40,10 @@ The `--allow-unrelated-histories` is necessary because the history from the skel Whenever a change is needed or desired for the general technique for packaging, it can be made in the skeleton project and then merged into each of the derived projects as needed, recommended before each release. As a result, features and best practices for packaging are centrally maintained and readily trickle into a whole suite of packages. This technique lowers the amount of tedious work necessary to create or maintain a project, and coupled with other techniques like continuous integration and deployment, lowers the cost of creating and maintaining refined Python projects to just a few, familiar Git operations. +For example, here's a session of the [path project](https://pypi.org/project/path) pulling non-conflicting changes from the skeleton: + + + Thereafter, the target project can make whatever customizations it deems relevant to the scaffolding. The project may even at some point decide that the divergence is too great to merit renewed merging with the original skeleton. This approach applies maximal guidance while creating minimal constraints. # Features From 6a13942eff3b62a7b4017101c33ae752e69fbc89 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 1 Sep 2020 22:00:43 -0400 Subject: [PATCH 348/447] Add the env var mapping too. --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2be55e73..fdad0e5d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -89,6 +89,7 @@ stages: tox -e release env: TWINE_PASSWORD: $(PyPI-token) + GITHUB_TOKEN: $(Github-token) displayName: 'publish to PyPI' condition: contains(variables['Build.SourceBranch'], 'tags') From 15f6272d1eb253940f82737b4f340365ee9879a9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 4 Sep 2020 18:33:17 -0400 Subject: [PATCH 349/447] Disable pytest-black and pytest-mypy on PyPy. Fixes jaraco/skeleton#22. Ref pytest-dev/pytest#7675. --- pyproject.toml | 8 ++++++++ setup.cfg | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6ee7df23..9b02ee75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,3 +6,11 @@ build-backend = "setuptools.build_meta" skip-string-normalization = true [tool.setuptools_scm] + +# jaraco/skeleton#22 +[tool.jaraco.pytest.opts.--black] +action = "store_true" + +# jaraco/skeleton#22 +[tool.jaraco.pytest.opts.--mypy] +action = "store_true" diff --git a/setup.cfg b/setup.cfg index e2dcebb1..e9dd1772 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,9 +26,11 @@ testing = pytest >= 3.5, !=3.7.3 pytest-checkdocs >= 1.2.3 pytest-flake8 - pytest-black >= 0.3.7 + pytest-black >= 0.3.7; python_implementation != "PyPy" pytest-cov - pytest-mypy + pytest-mypy; python_implementation != "PyPy" + # jaraco/skeleton#22 + jaraco.test >= 3 # local From 38207546aea22e38433a316c6ad9bf024de61ef3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 8 Sep 2020 19:55:53 -0400 Subject: [PATCH 350/447] Bump black and blacken-docs to latest stable versions. --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fe46b8c5..6639c78c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,10 @@ repos: - repo: https://github.com/psf/black - rev: 19.10b0 + rev: stable hooks: - id: black - repo: https://github.com/asottile/blacken-docs - rev: v1.4.0 + rev: v1.8.0 hooks: - id: blacken-docs From 8117892ea5da9969f49756933bcbaa576ee7c5d7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 12 Sep 2020 14:18:23 -0400 Subject: [PATCH 351/447] Use enabled plugin configuration to enable mypy and black when the plugin is present. Ref jaraco/skeleton#22. --- pyproject.toml | 8 ++++---- pytest.ini | 2 +- setup.cfg | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9b02ee75..9cd13ba4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,9 +8,9 @@ skip-string-normalization = true [tool.setuptools_scm] # jaraco/skeleton#22 -[tool.jaraco.pytest.opts.--black] -action = "store_true" +[tool.jaraco.pytest.plugins.black] +addopts = "--black" # jaraco/skeleton#22 -[tool.jaraco.pytest.opts.--mypy] -action = "store_true" +[tool.jaraco.pytest.plugins.mypy] +addopts = "--mypy" diff --git a/pytest.ini b/pytest.ini index 381b3271..5ffd7f73 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] norecursedirs=dist build .tox .eggs -addopts=--doctest-modules --flake8 --black --cov --mypy +addopts=--doctest-modules --flake8 --cov doctest_optionflags=ALLOW_UNICODE ELLIPSIS # workaround for warning pytest-dev/pytest#6178 junit_family=xunit2 diff --git a/setup.cfg b/setup.cfg index e9dd1772..eb834fef 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,7 @@ testing = pytest-cov pytest-mypy; python_implementation != "PyPy" # jaraco/skeleton#22 - jaraco.test >= 3 + jaraco.test >= 3.1.1 # local From 678e1a973a0139c0e0ab40395dfbada6c3ea72b9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 12 Sep 2020 19:02:55 -0400 Subject: [PATCH 352/447] Also enable flake8 and cov when the plugins are present. --- pyproject.toml | 6 ++++++ pytest.ini | 2 +- setup.cfg | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9cd13ba4..79f088a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,3 +14,9 @@ addopts = "--black" # jaraco/skeleton#22 [tool.jaraco.pytest.plugins.mypy] addopts = "--mypy" + +[tool.jaraco.pytest.plugins.flake8] +addopts = "--flake8" + +[tool.jaraco.pytest.plugins.cov] +addopts = "--cov" diff --git a/pytest.ini b/pytest.ini index 5ffd7f73..d7f0b115 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] norecursedirs=dist build .tox .eggs -addopts=--doctest-modules --flake8 --cov +addopts=--doctest-modules doctest_optionflags=ALLOW_UNICODE ELLIPSIS # workaround for warning pytest-dev/pytest#6178 junit_family=xunit2 diff --git a/setup.cfg b/setup.cfg index eb834fef..6321ca77 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,7 @@ testing = pytest-cov pytest-mypy; python_implementation != "PyPy" # jaraco/skeleton#22 - jaraco.test >= 3.1.1 + jaraco.test >= 3.2.0 # local From ea912cf6598bd882a723518eb5cc01f1c8397094 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 Oct 2020 18:09:29 -0400 Subject: [PATCH 353/447] Add workflows for running tests. Ref jaraco/skeleton#24. --- .github/workflows/main.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..a1897983 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,22 @@ +name: Main + +on: [push, pull_request] + +jobs: + test: + strategy: + matrix: + python: [3.6, 3.7, 3.8] + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python }} + - name: Install tox + run: | + python -m pip install tox + - name: Run tests + run: tox From ce34be2f7548dd68f74e5a0fb98f5b796b076900 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 10 Oct 2020 19:55:49 -0400 Subject: [PATCH 354/447] Cut releases from Github Actions instead of Azure Pipelines. Ref jaraco/skeleton#24. --- .github/workflows/main.yml | 22 +++++++++++++++++++++- azure-pipelines.yml | 23 ----------------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a1897983..b3dd81fc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Main +name: Automated Tests on: [push, pull_request] @@ -20,3 +20,23 @@ jobs: python -m pip install tox - name: Run tests run: tox + + release: + needs: test + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install tox + run: | + python -m pip install tox + - name: Release + run: tox -e release + env: + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index fdad0e5d..6d318994 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -70,26 +70,3 @@ stages: testResultsFiles: '**/test-results.xml' testRunTitle: 'Python $(python.version)' condition: succeededOrFailed() - -- stage: Publish - dependsOn: Test - jobs: - - job: 'Publish' - - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '3.8' - architecture: 'x64' - - - script: python -m pip install tox - displayName: 'Install tox' - - - script: | - tox -e release - env: - TWINE_PASSWORD: $(PyPI-token) - GITHUB_TOKEN: $(Github-token) - displayName: 'publish to PyPI' - - condition: contains(variables['Build.SourceBranch'], 'tags') From dcd7cbda05754898cc9723c9b36e41a92cb3e139 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 11 Oct 2020 06:12:09 -0400 Subject: [PATCH 355/447] Refresh docs to prefer Github Actions to Azure Pipelines. Ref jaraco/skeleton#24. --- skeleton.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/skeleton.md b/skeleton.md index 17a94ed7..c6b0cd0c 100644 --- a/skeleton.md +++ b/skeleton.md @@ -56,6 +56,7 @@ The features/techniques employed by the skeleton include: - A README.rst as reStructuredText with some popular badges, but with Read the Docs and AppVeyor badges commented out - A CHANGES.rst file intended for publishing release notes about the project - Use of [Black](https://black.readthedocs.io/en/stable/) for code formatting (disabled on unsupported Python 3.5 and earlier) +- Integrated type checking through [mypy](https://github.com/python/mypy/). ## Packaging Conventions @@ -109,9 +110,20 @@ Relies on a .flake8 file to correct some default behaviors: The project is pre-configured to run tests through multiple CI providers. +### Github Actions + +[Github Actions](https://docs.github.com/en/free-pro-team@latest/actions) are the preferred provider as they provide free, fast, multi-platform services with straightforward configuration. Configured in `.github/workflows`. + +Features include: +- test against multiple Python versions +- run on late (and updated) platform versions +- automated releases of tagged commits + ### Azure Pipelines -[Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/) are the preferred provider as they provide free, fast, multi-platform services. See azure-pipelines.yml for more details. +[Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/) were adopted for free, fast, multi-platform services. See azure-pipelines.yml for more details. + +Azure Pipelines require many [complicated setup steps](https://github.com/Azure/azure-devops-cli-extension/issues/968) that have not been readily automated. Features include: @@ -133,20 +145,13 @@ A minimal template for running under AppVeyor (Windows) is provided. ### Continuous Deployments -In addition to running tests, an additional deploy stage is configured to automatically release tagged commits to PyPI using [API tokens](https://pypi.org/help/#apitoken). The release process expects an authorized token to be configured with Azure as the `Azure secrets` variable group. This variable group needs to be created only once per organization. For example: +In addition to running tests, an additional publish stage is configured to automatically release tagged commits to PyPI using [API tokens](https://pypi.org/help/#apitoken). The release process expects an authorized token to be configured with each Github project (or org) `PYPI_TOKEN` [secret](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets). Example: ``` -# create a resource group if none exists -az group create --name main --location eastus2 -# create the vault (try different names until something works) -az keyvault create --name secrets007 --resource-group main -# create the secret -az keyvault secret set --vault-name secrets007 --name PyPI-token --value $token +pip-run -q setuptools jaraco.develop -- -m jaraco.develop.add-github-secret PYPI_TOKEN $TOKEN --project org/repo ``` -Then, in the web UI for the project's Pipelines Library, create the `Azure secrets` variable group referencing the key vault name. - -For more details, see [this blog entry](https://blog.jaraco.com/configuring-azure-pipelines-with-secets/). + ## Building Documentation From 5474714ed2622af66674fafe62ae01180c0adf81 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Oct 2020 14:20:21 -0400 Subject: [PATCH 356/447] Use RTD v2 config --- .readthedocs.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 8ae44684..cc698548 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,5 +1,6 @@ +version: 2 python: - version: 3 - extra_requirements: - - docs - pip_install: true + install: + - path: . + extra_requirements: + - docs From 6ad08b8489ac1c1eba37d32525fa7fa8465076c9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 22 Oct 2020 14:08:14 -0400 Subject: [PATCH 357/447] Test on Python 3.9. Skip 3.7 to avoid creating too many builds. Release on 3.9. --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b3dd81fc..8c5c232c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,13 +6,13 @@ jobs: test: strategy: matrix: - python: [3.6, 3.7, 3.8] + python: [3.6, 3.8, 3.9] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - name: Setup Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - name: Install tox @@ -29,9 +29,9 @@ jobs: steps: - uses: actions/checkout@v2 - name: Setup Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.9 - name: Install tox run: | python -m pip install tox From ca9ad41aeb98be511d9451706a9e29dd5016df00 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 22 Oct 2020 14:09:25 -0400 Subject: [PATCH 358/447] Drop tests on Travis, Appveyor, and Azure Pipelines. --- .travis.yml | 19 ------------ README.rst | 11 ++----- appveyor.yml | 24 --------------- azure-pipelines.yml | 72 --------------------------------------------- skeleton.md | 26 +--------------- 5 files changed, 4 insertions(+), 148 deletions(-) delete mode 100644 .travis.yml delete mode 100644 appveyor.yml delete mode 100644 azure-pipelines.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index eed7b0a4..00000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -dist: bionic -language: python - -python: -- 3.6 -- &latest_py3 3.8 - -cache: pip - -install: -# ensure virtualenv is upgraded to avoid issues like jaraco/path#188 -- pip install -U --upgrade-strategy=eager tox - -before_script: - # Enable IPv6. Ref travis-ci/travis-ci#8361 - - if [ "${TRAVIS_OS_NAME}" == "linux" ]; then - sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'; - fi -script: tox diff --git a/README.rst b/README.rst index 4c7fd554..69554ef8 100644 --- a/README.rst +++ b/README.rst @@ -6,18 +6,13 @@ .. _PyPI link: https://pypi.org/project/skeleton -.. image:: https://dev.azure.com/jaraco/skeleton/_apis/build/status/jaraco.skeleton?branchName=master - :target: https://dev.azure.com/jaraco/skeleton/_build/latest?definitionId=1&branchName=master - -.. image:: https://img.shields.io/travis/jaraco/skeleton/master.svg - :target: https://travis-ci.org/jaraco/skeleton +.. image:: https://github.com/jaraco/skeleton/workflows/Automated%20Tests/badge.svg + :target: https://github.com/jaraco/skeleton/actions?query=workflow%3A%22Automated+Tests%22 + :alt: Automated Tests .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black :alt: Code style: Black -.. .. image:: https://img.shields.io/appveyor/ci/jaraco/skeleton/master.svg -.. :target: https://ci.appveyor.com/project/jaraco/skeleton/branch/master - .. .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest .. :target: https://skeleton.readthedocs.io/en/latest/?badge=latest diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index c6f46e4f..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,24 +0,0 @@ -environment: - - APPVEYOR: true - - matrix: - - PYTHON: "C:\\Python36-x64" - - PYTHON: "C:\\Python38-x64" - -install: - # symlink python from a directory with a space - - "mklink /d \"C:\\Program Files\\Python\" %PYTHON%" - - "SET PYTHON=\"C:\\Program Files\\Python\"" - - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - -build: off - -cache: - - '%LOCALAPPDATA%\pip\Cache' - -test_script: - - "python -m pip install -U tox virtualenv" - - "tox" - -version: '{build}' diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 6d318994..00000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,72 +0,0 @@ -# Create the project in Azure with: -# az devops project create --name $name --organization https://dev.azure.com/$org/ --visibility public -# then configure the pipelines (through web UI) - -trigger: - branches: - include: - - '*' - tags: - include: - - '*' - -pool: - vmImage: $(pool_vm_image) - -variables: -- group: Azure secrets -- name: pool_vm_image - value: Ubuntu-18.04 - -stages: -- stage: Test - jobs: - - - job: 'Test' - strategy: - matrix: - Bionic Python 3.6: - python.version: '3.6' - Bionic Python 3.8: - python.version: '3.8' - Windows Python 3.8: - python.version: '3.8' - pool_vm_image: vs2017-win2016 - Windows Python Prerelease: - python.version: '3.9' - pool_vm_image: vs2017-win2016 - MacOS: - python.version: '3.8' - pool_vm_image: macos-10.15 - - maxParallel: 4 - - steps: - - task: NuGetToolInstaller@1 - displayName: 'Install NuGet' - condition: eq(variables['pool_vm_image'], 'vs2017-win2016') - - - powershell: | - nuget install python -Prerelease -OutputDirectory "$(Build.BinariesDirectory)" -ExcludeVersion -NonInteractive - Write-Host "##vso[task.prependpath]$(Build.BinariesDirectory)\python\tools" - Write-Host "##vso[task.prependpath]$(Build.BinariesDirectory)\python\tools\Scripts" - condition: and(succeeded(), and(eq(variables['python.version'], '3.9'), eq(variables['pool_vm_image'], 'vs2017-win2016'))) - - - task: UsePythonVersion@0 - inputs: - versionSpec: '$(python.version)' - architecture: 'x64' - condition: and(succeeded(), ne(variables['python.version'], '3.9')) - - - script: python -m pip install tox - displayName: 'Install tox' - - - script: | - tox -- --junit-xml=test-results.xml - displayName: 'run tests' - - - task: PublishTestResults@2 - inputs: - testResultsFiles: '**/test-results.xml' - testRunTitle: 'Python $(python.version)' - condition: succeededOrFailed() diff --git a/skeleton.md b/skeleton.md index c6b0cd0c..7c3956c7 100644 --- a/skeleton.md +++ b/skeleton.md @@ -108,7 +108,7 @@ Relies on a .flake8 file to correct some default behaviors: ## Continuous Integration -The project is pre-configured to run tests through multiple CI providers. +The project is pre-configured to run Continuous Integration tests. ### Github Actions @@ -119,30 +119,6 @@ Features include: - run on late (and updated) platform versions - automated releases of tagged commits -### Azure Pipelines - -[Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/) were adopted for free, fast, multi-platform services. See azure-pipelines.yml for more details. - -Azure Pipelines require many [complicated setup steps](https://github.com/Azure/azure-devops-cli-extension/issues/968) that have not been readily automated. - -Features include: - -- test against multiple Python versions -- run on Ubuntu Bionic - -### Travis CI - -[Travis CI](https://travis-ci.org) is configured through .travis.yml. Any new project must be enabled either through their web site or with the `travis enable` command. - -Features include: -- test against Python 3 -- run on Ubuntu Bionic -- correct for broken IPv6 - -### AppVeyor - -A minimal template for running under AppVeyor (Windows) is provided. - ### Continuous Deployments In addition to running tests, an additional publish stage is configured to automatically release tagged commits to PyPI using [API tokens](https://pypi.org/help/#apitoken). The release process expects an authorized token to be configured with each Github project (or org) `PYPI_TOKEN` [secret](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets). Example: From 1311cecaad5e176eb7604a045d16dcd6c7353a45 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 23 Oct 2020 10:18:23 -0400 Subject: [PATCH 359/447] use add-github-secrets, which infers the secrets needed from the github workflow. --- skeleton.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/skeleton.md b/skeleton.md index 7c3956c7..ec421c25 100644 --- a/skeleton.md +++ b/skeleton.md @@ -124,11 +124,9 @@ Features include: In addition to running tests, an additional publish stage is configured to automatically release tagged commits to PyPI using [API tokens](https://pypi.org/help/#apitoken). The release process expects an authorized token to be configured with each Github project (or org) `PYPI_TOKEN` [secret](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets). Example: ``` -pip-run -q setuptools jaraco.develop -- -m jaraco.develop.add-github-secret PYPI_TOKEN $TOKEN --project org/repo +pip-run -q jaraco.develop -- -m jaraco.develop.add-github-secrets ``` - - ## Building Documentation Documentation is automatically built by [Read the Docs](https://readthedocs.org) when the project is registered with it, by way of the .readthedocs.yml file. To test the docs build manually, a tox env may be invoked as `tox -e docs`. Both techniques rely on the dependencies declared in `setup.cfg/options.extras_require.docs`. From 95ce6f33cc095df6d0a5f239e075a610eefbe262 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 28 Oct 2020 20:58:20 -0400 Subject: [PATCH 360/447] Use inline flags with local scope. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 41b53557..433d185d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,7 +14,7 @@ url='{package_url}/issues/{issue}', ), dict( - pattern=r'^(?m)((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n', + pattern=r'(?m:^((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n)', with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', ), dict( From 27f7c53a66c077cea17896496330bab97f1db54b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 19 Nov 2020 22:02:41 -0500 Subject: [PATCH 361/447] Honor TOX_WORK_DIR if set. Workaround for tox-dev/tox#20. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 7347d95f..7233b942 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,7 @@ envlist = python minversion = 3.2 # https://github.com/jaraco/skeleton/issues/6 tox_pip_extensions_ext_venv_update = true +toxworkdir={env:TOX_WORK_DIR:.tox} [testenv] From c681f6748acaea1bf0b706528c36327cc94a6eed Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 8 Dec 2020 16:29:09 -0500 Subject: [PATCH 362/447] Collapse skeleton history from archive/2020-12 --- .coveragerc | 5 ++ .flake8 | 9 +++ .github/workflows/main.yml | 42 +++++++++++ .pre-commit-config.yaml | 10 +++ .readthedocs.yml | 6 ++ CHANGES.rst | 0 LICENSE | 19 +++++ README.rst | 18 +++++ docs/conf.py | 26 +++++++ docs/history.rst | 8 +++ docs/index.rst | 22 ++++++ mypy.ini | 2 + pyproject.toml | 22 ++++++ pytest.ini | 9 +++ setup.cfg | 45 ++++++++++++ setup.py | 6 ++ skeleton.md | 144 +++++++++++++++++++++++++++++++++++++ tox.ini | 40 +++++++++++ 18 files changed, 433 insertions(+) create mode 100644 .coveragerc create mode 100644 .flake8 create mode 100644 .github/workflows/main.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .readthedocs.yml create mode 100644 CHANGES.rst create mode 100644 LICENSE create mode 100644 README.rst create mode 100644 docs/conf.py create mode 100644 docs/history.rst create mode 100644 docs/index.rst create mode 100644 mypy.ini create mode 100644 pyproject.toml create mode 100644 pytest.ini create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 skeleton.md create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..45823064 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[run] +omit = .tox/* + +[report] +show_missing = True diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..790c109f --- /dev/null +++ b/.flake8 @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 88 +ignore = + # W503 violates spec https://github.com/PyCQA/pycodestyle/issues/513 + W503 + # W504 has issues https://github.com/OCA/maintainer-quality-tools/issues/545 + W504 + # Black creates whitespace before colon + E203 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..8c5c232c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,42 @@ +name: Automated Tests + +on: [push, pull_request] + +jobs: + test: + strategy: + matrix: + python: [3.6, 3.8, 3.9] + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install tox + run: | + python -m pip install tox + - name: Run tests + run: tox + + release: + needs: test + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install tox + run: | + python -m pip install tox + - name: Release + run: tox -e release + env: + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..6639c78c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: +- repo: https://github.com/psf/black + rev: stable + hooks: + - id: black + +- repo: https://github.com/asottile/blacken-docs + rev: v1.8.0 + hooks: + - id: blacken-docs diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..cc698548 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,6 @@ +version: 2 +python: + install: + - path: . + extra_requirements: + - docs diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 00000000..e69de29b diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..353924be --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright Jason R. Coombs + +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. diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..69554ef8 --- /dev/null +++ b/README.rst @@ -0,0 +1,18 @@ +.. image:: https://img.shields.io/pypi/v/skeleton.svg + :target: `PyPI link`_ + +.. image:: https://img.shields.io/pypi/pyversions/skeleton.svg + :target: `PyPI link`_ + +.. _PyPI link: https://pypi.org/project/skeleton + +.. image:: https://github.com/jaraco/skeleton/workflows/Automated%20Tests/badge.svg + :target: https://github.com/jaraco/skeleton/actions?query=workflow%3A%22Automated+Tests%22 + :alt: Automated Tests + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: Black + +.. .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest +.. :target: https://skeleton.readthedocs.io/en/latest/?badge=latest diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..433d185d --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +extensions = ['sphinx.ext.autodoc', 'jaraco.packaging.sphinx', 'rst.linker'] + +master_doc = "index" + +link_files = { + '../CHANGES.rst': dict( + using=dict(GH='https://github.com'), + replace=[ + dict( + pattern=r'(Issue #|\B#)(?P\d+)', + url='{package_url}/issues/{issue}', + ), + dict( + pattern=r'(?m:^((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n)', + with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', + ), + dict( + pattern=r'PEP[- ](?P\d+)', + url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/', + ), + ], + ) +} diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 00000000..8e217503 --- /dev/null +++ b/docs/history.rst @@ -0,0 +1,8 @@ +:tocdepth: 2 + +.. _changes: + +History +******* + +.. include:: ../CHANGES (links).rst diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..d14131b0 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,22 @@ +Welcome to skeleton documentation! +======================================== + +.. toctree:: + :maxdepth: 1 + + history + + +.. automodule:: skeleton + :members: + :undoc-members: + :show-inheritance: + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..976ba029 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +ignore_missing_imports = True diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..79f088a9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4.1"] +build-backend = "setuptools.build_meta" + +[tool.black] +skip-string-normalization = true + +[tool.setuptools_scm] + +# jaraco/skeleton#22 +[tool.jaraco.pytest.plugins.black] +addopts = "--black" + +# jaraco/skeleton#22 +[tool.jaraco.pytest.plugins.mypy] +addopts = "--mypy" + +[tool.jaraco.pytest.plugins.flake8] +addopts = "--flake8" + +[tool.jaraco.pytest.plugins.cov] +addopts = "--cov" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..d7f0b115 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,9 @@ +[pytest] +norecursedirs=dist build .tox .eggs +addopts=--doctest-modules +doctest_optionflags=ALLOW_UNICODE ELLIPSIS +# workaround for warning pytest-dev/pytest#6178 +junit_family=xunit2 +filterwarnings= + # https://github.com/pytest-dev/pytest/issues/6928 + ignore:direct construction of .*Item has been deprecated:DeprecationWarning diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..6321ca77 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,45 @@ +[metadata] +license_file = LICENSE +name = skeleton +author = Jason R. Coombs +author_email = jaraco@jaraco.com +description = skeleton +long_description = file:README.rst +url = https://github.com/jaraco/skeleton +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + +[options] +packages = find: +include_package_data = true +python_requires = >=3.6 +install_requires = +setup_requires = setuptools_scm[toml] >= 3.4.1 + +[options.extras_require] +testing = + # upstream + pytest >= 3.5, !=3.7.3 + pytest-checkdocs >= 1.2.3 + pytest-flake8 + pytest-black >= 0.3.7; python_implementation != "PyPy" + pytest-cov + pytest-mypy; python_implementation != "PyPy" + # jaraco/skeleton#22 + jaraco.test >= 3.2.0 + + # local + +docs = + # upstream + sphinx + jaraco.packaging >= 3.2 + rst.linker >= 1.9 + + # local + +[options.entry_points] diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..bac24a43 --- /dev/null +++ b/setup.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +import setuptools + +if __name__ == "__main__": + setuptools.setup() diff --git a/skeleton.md b/skeleton.md new file mode 100644 index 00000000..ec421c25 --- /dev/null +++ b/skeleton.md @@ -0,0 +1,144 @@ +# Overview + +This project is merged with [skeleton](https://github.com/jaraco/skeleton). What is skeleton? It's the scaffolding of a Python project jaraco [introduced in his blog](https://blog.jaraco.com/a-project-skeleton-for-python-projects/). It seeks to provide a means to re-use techniques and inherit advances when managing projects for distribution. + +## An SCM-Managed Approach + +While maintaining dozens of projects in PyPI, jaraco derives best practices for project distribution and publishes them in the [skeleton repo](https://github.com/jaraco/skeleton), a Git repo capturing the evolution and culmination of these best practices. + +It's intended to be used by a new or existing project to adopt these practices and honed and proven techniques. Adopters are encouraged to use the project directly and maintain a small deviation from the technique, make their own fork for more substantial changes unique to their environment or preferences, or simply adopt the skeleton once and abandon it thereafter. + +The primary advantage to using an SCM for maintaining these techniques is that those tools help facilitate the merge between the template and its adopting projects. + +Another advantage to using an SCM-managed approach is that tools like GitHub recognize that a change in the skeleton is the _same change_ across all projects that merge with that skeleton. Without the ancestry, with a traditional copy/paste approach, a [commit like this](https://github.com/jaraco/skeleton/commit/12eed1326e1bc26ce256e7b3f8cd8d3a5beab2d5) would produce notifications in the upstream project issue for each and every application, but because it's centralized, GitHub provides just the one notification when the change is added to the skeleton. + +# Usage + +## new projects + +To use skeleton for a new project, simply pull the skeleton into a new project: + +``` +$ git init my-new-project +$ cd my-new-project +$ git pull gh://jaraco/skeleton +``` + +Now customize the project to suit your individual project needs. + +## existing projects + +If you have an existing project, you can still incorporate the skeleton by merging it into the codebase. + +``` +$ git merge skeleton --allow-unrelated-histories +``` + +The `--allow-unrelated-histories` is necessary because the history from the skeleton was previously unrelated to the existing codebase. Resolve any merge conflicts and commit to the master, and now the project is based on the shared skeleton. + +## Updating + +Whenever a change is needed or desired for the general technique for packaging, it can be made in the skeleton project and then merged into each of the derived projects as needed, recommended before each release. As a result, features and best practices for packaging are centrally maintained and readily trickle into a whole suite of packages. This technique lowers the amount of tedious work necessary to create or maintain a project, and coupled with other techniques like continuous integration and deployment, lowers the cost of creating and maintaining refined Python projects to just a few, familiar Git operations. + +For example, here's a session of the [path project](https://pypi.org/project/path) pulling non-conflicting changes from the skeleton: + + + +Thereafter, the target project can make whatever customizations it deems relevant to the scaffolding. The project may even at some point decide that the divergence is too great to merit renewed merging with the original skeleton. This approach applies maximal guidance while creating minimal constraints. + +# Features + +The features/techniques employed by the skeleton include: + +- PEP 517/518-based build relying on Setuptools as the build tool +- Setuptools declarative configuration using setup.cfg +- tox for running tests +- A README.rst as reStructuredText with some popular badges, but with Read the Docs and AppVeyor badges commented out +- A CHANGES.rst file intended for publishing release notes about the project +- Use of [Black](https://black.readthedocs.io/en/stable/) for code formatting (disabled on unsupported Python 3.5 and earlier) +- Integrated type checking through [mypy](https://github.com/python/mypy/). + +## Packaging Conventions + +A pyproject.toml is included to enable PEP 517 and PEP 518 compatibility and declares the requirements necessary to build the project on Setuptools (a minimum version compatible with setup.cfg declarative config). + +The setup.cfg file implements the following features: + +- Assumes universal wheel for release +- Advertises the project's LICENSE file (MIT by default) +- Reads the README.rst file into the long description +- Some common Trove classifiers +- Includes all packages discovered in the repo +- Data files in the package are also included (not just Python files) +- Declares the required Python versions +- Declares install requirements (empty by default) +- Declares setup requirements for legacy environments +- Supplies two 'extras': + - testing: requirements for running tests + - docs: requirements for building docs + - these extras split the declaration into "upstream" (requirements as declared by the skeleton) and "local" (those specific to the local project); these markers help avoid merge conflicts +- Placeholder for defining entry points + +Additionally, the setup.py file declares `use_scm_version` which relies on [setuptools_scm](https://pypi.org/project/setuptools_scm) to do two things: + +- derive the project version from SCM tags +- ensure that all files committed to the repo are automatically included in releases + +## Running Tests + +The skeleton assumes the developer has [tox](https://pypi.org/project/tox) installed. The developer is expected to run `tox` to run tests on the current Python version using [pytest](https://pypi.org/project/pytest). + +Other environments (invoked with `tox -e {name}`) supplied include: + + - a `docs` environment to build the documentation + - a `release` environment to publish the package to PyPI + +A pytest.ini is included to define common options around running tests. In particular: + +- rely on default test discovery in the current directory +- avoid recursing into common directories not containing tests +- run doctests on modules and invoke Flake8 tests +- in doctests, allow Unicode literals and regular literals to match, allowing for doctests to run on Python 2 and 3. Also enable ELLIPSES, a default that would be undone by supplying the prior option. +- filters out known warnings caused by libraries/functionality included by the skeleton + +Relies on a .flake8 file to correct some default behaviors: + +- disable mutually incompatible rules W503 and W504 +- support for Black format + +## Continuous Integration + +The project is pre-configured to run Continuous Integration tests. + +### Github Actions + +[Github Actions](https://docs.github.com/en/free-pro-team@latest/actions) are the preferred provider as they provide free, fast, multi-platform services with straightforward configuration. Configured in `.github/workflows`. + +Features include: +- test against multiple Python versions +- run on late (and updated) platform versions +- automated releases of tagged commits + +### Continuous Deployments + +In addition to running tests, an additional publish stage is configured to automatically release tagged commits to PyPI using [API tokens](https://pypi.org/help/#apitoken). The release process expects an authorized token to be configured with each Github project (or org) `PYPI_TOKEN` [secret](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets). Example: + +``` +pip-run -q jaraco.develop -- -m jaraco.develop.add-github-secrets +``` + +## Building Documentation + +Documentation is automatically built by [Read the Docs](https://readthedocs.org) when the project is registered with it, by way of the .readthedocs.yml file. To test the docs build manually, a tox env may be invoked as `tox -e docs`. Both techniques rely on the dependencies declared in `setup.cfg/options.extras_require.docs`. + +In addition to building the Sphinx docs scaffolded in `docs/`, the docs build a `history.html` file that first injects release dates and hyperlinks into the CHANGES.rst before incorporating it as history in the docs. + +## Cutting releases + +By default, tagged commits are released through the continuous integration deploy stage. + +Releases may also be cut manually by invoking the tox environment `release` with the PyPI token set as the TWINE_PASSWORD: + +``` +TWINE_PASSWORD={token} tox -e release +``` diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..7233b942 --- /dev/null +++ b/tox.ini @@ -0,0 +1,40 @@ +[tox] +envlist = python +minversion = 3.2 +# https://github.com/jaraco/skeleton/issues/6 +tox_pip_extensions_ext_venv_update = true +toxworkdir={env:TOX_WORK_DIR:.tox} + + +[testenv] +deps = +commands = + pytest {posargs} +usedevelop = True +extras = testing + +[testenv:docs] +extras = + docs + testing +changedir = docs +commands = + python -m sphinx . {toxinidir}/build/html + +[testenv:release] +skip_install = True +deps = + pep517>=0.5 + twine[keyring]>=1.13 + path + jaraco.develop>=7.1 +passenv = + TWINE_PASSWORD + GITHUB_TOKEN +setenv = + TWINE_USERNAME = {env:TWINE_USERNAME:__token__} +commands = + python -c "import path; path.Path('dist').rmtree_p()" + python -m pep517.build . + python -m twine upload dist/* + python -m jaraco.develop.create-github-release From 2667241f44fed464948cbd140bed1b17cfe4e826 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 12 Dec 2020 23:29:03 -0500 Subject: [PATCH 363/447] Update skeleton description to describe the periodic collapse. Fixes #27. --- skeleton.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/skeleton.md b/skeleton.md index ec421c25..dd8ec014 100644 --- a/skeleton.md +++ b/skeleton.md @@ -46,6 +46,26 @@ For example, here's a session of the [path project](https://pypi.org/project/pat Thereafter, the target project can make whatever customizations it deems relevant to the scaffolding. The project may even at some point decide that the divergence is too great to merit renewed merging with the original skeleton. This approach applies maximal guidance while creating minimal constraints. +## Periodic Collapse + +In late 2020, this project [introduced](https://github.com/jaraco/skeleton/issues/27) the idea of a periodic but infrequent (O(years)) collapse of commits to limit the number of commits a new consumer will need to accept to adopt the skeleton. + +The full history of commits is collapsed into a single commit and that commit becomes the new mainline head. + +When one of these collapse operations happens, any project that previously pulled from the skeleton will no longer have a related history with that new main branch. For those projects, the skeleton provides a "handoff" branch that reconciles the two branches. Any project that has previously merged with the skeleton but now gets an error "fatal: refusing to merge unrelated histories" should instead use the handoff branch once to incorporate the new main branch. + +``` +$ git pull https://github.com/jaraco/skeleton 2020-handoff +``` + +This handoff needs to be pulled just once and thereafter the project can pull from the main head. + +The archive and handoff branches from prior collapses are indicate here: + +| refresh | archive | handoff | +|---------|-----------------|--------------| +| 2020-12 | archive/2020-12 | 2020-handoff | + # Features The features/techniques employed by the skeleton include: From 150321caba0dc73489b61d6b5bbfbed52b795ae7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 13 Dec 2020 14:03:23 -0500 Subject: [PATCH 364/447] Enable automerge --- .github/workflows/automerge.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/automerge.yml diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 00000000..4f70acfb --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,27 @@ +name: automerge +on: + pull_request: + types: + - labeled + - unlabeled + - synchronize + - opened + - edited + - ready_for_review + - reopened + - unlocked + pull_request_review: + types: + - submitted + check_suite: + types: + - completed + status: {} +jobs: + automerge: + runs-on: ubuntu-latest + steps: + - name: automerge + uses: "pascalgn/automerge-action@v0.12.0" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" From 4b1334629e1cb254a1b6853f045f2615b79ec9e1 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 29 Dec 2020 09:56:52 -0500 Subject: [PATCH 365/447] Automatically inject project name in docs heading. --- docs/index.rst | 4 ++-- setup.cfg | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index d14131b0..325842bb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,5 @@ -Welcome to skeleton documentation! -======================================== +Welcome to |project| documentation! +=================================== .. toctree:: :maxdepth: 1 diff --git a/setup.cfg b/setup.cfg index 6321ca77..4fc095b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,7 +37,7 @@ testing = docs = # upstream sphinx - jaraco.packaging >= 3.2 + jaraco.packaging >= 8.2 rst.linker >= 1.9 # local From cfe99a5a7941f9f8785dd3ec12d1df94e9134411 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 29 Dec 2020 21:27:53 -0500 Subject: [PATCH 366/447] pre-commit autoupdate --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6639c78c..c15ab0c9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,10 @@ repos: - repo: https://github.com/psf/black - rev: stable + rev: 20.8b1 hooks: - id: black - repo: https://github.com/asottile/blacken-docs - rev: v1.8.0 + rev: v1.9.1 hooks: - id: blacken-docs From 060d491a9aaacfe457ad365cfd60b611fc9f5bcf Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 30 Dec 2020 10:57:25 -0500 Subject: [PATCH 367/447] Rename 'Automated Tests' to simply 'tests' --- .github/workflows/main.yml | 2 +- README.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c5c232c..6a8ff006 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Automated Tests +name: tests on: [push, pull_request] diff --git a/README.rst b/README.rst index 69554ef8..128e61e4 100644 --- a/README.rst +++ b/README.rst @@ -6,9 +6,9 @@ .. _PyPI link: https://pypi.org/project/skeleton -.. image:: https://github.com/jaraco/skeleton/workflows/Automated%20Tests/badge.svg - :target: https://github.com/jaraco/skeleton/actions?query=workflow%3A%22Automated+Tests%22 - :alt: Automated Tests +.. image:: https://github.com/jaraco/skeleton/workflows/tests/badge.svg + :target: https://github.com/jaraco/skeleton/actions?query=workflow%3A%22tests%22 + :alt: tests .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black From 2b839bad1c2189f4eeb0f74c4a2455ba6687741b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 30 Dec 2020 12:06:13 -0500 Subject: [PATCH 368/447] Add note about automatic merging of PRs and the requirements and limitations. --- skeleton.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skeleton.md b/skeleton.md index dd8ec014..0938f892 100644 --- a/skeleton.md +++ b/skeleton.md @@ -138,6 +138,8 @@ Features include: - test against multiple Python versions - run on late (and updated) platform versions - automated releases of tagged commits +- [automatic merging of PRs](https://github.com/marketplace/actions/merge-pull-requests) (requires [protecting branches with required status checks](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/enabling-required-status-checks), [not possible through API](https://github.community/t/set-all-status-checks-to-be-required-as-branch-protection-using-the-github-api/119493)) + ### Continuous Deployments From a36768aa363c8f7b54aae00e11f895ff06337532 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 30 Dec 2020 22:20:46 -0500 Subject: [PATCH 369/447] Prefer pytest-enabler to jaraco.test --- pyproject.toml | 10 ++++------ setup.cfg | 3 +-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 79f088a9..b6ebc0be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,16 +7,14 @@ skip-string-normalization = true [tool.setuptools_scm] -# jaraco/skeleton#22 -[tool.jaraco.pytest.plugins.black] +[pytest.enabler.black] addopts = "--black" -# jaraco/skeleton#22 -[tool.jaraco.pytest.plugins.mypy] +[pytest.enabler.mypy] addopts = "--mypy" -[tool.jaraco.pytest.plugins.flake8] +[pytest.enabler.flake8] addopts = "--flake8" -[tool.jaraco.pytest.plugins.cov] +[pytest.enabler.cov] addopts = "--cov" diff --git a/setup.cfg b/setup.cfg index 4fc095b3..d5010f70 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,8 +29,7 @@ testing = pytest-black >= 0.3.7; python_implementation != "PyPy" pytest-cov pytest-mypy; python_implementation != "PyPy" - # jaraco/skeleton#22 - jaraco.test >= 3.2.0 + pytest-enabler # local From b5da3df83986512b5f7637f1cca0cbe28fd39f4a Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Fri, 1 Jan 2021 20:03:45 +0000 Subject: [PATCH 370/447] Remove unwanted workflow. --- .github/workflows/ci.yml | 85 ---------------------------------------- 1 file changed, 85 deletions(-) delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 9892a128..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: CI - -on: - push: - branches: - - '*' - tags: - - '*' - # Empty pull request argument means "all pull-requests" - pull_request: - -jobs: - # 1. linters - check-lint: - strategy: - matrix: - include: - - name: flake8 - tox-env: flake8 - - name: pyupgrade - tox-env: pyupgrade - - name: Check ${{ matrix.name }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: 3.x - - name: Install dependencies - run: | - set -xeu - python --version - pip install tox - - name: Check ${{ matrix.name }} - run: tox -e ${{ matrix.tox-env }} - - # 2. Unit tests - tests: - strategy: - matrix: - include: - - python-version: 3.6 - tox-env: py36 - - python-version: 3.7 - tox-env: py37 - - python-version: 3.8 - tox-env: py38 - - python-version: 3.9 - tox-env: py39 - - name: Test (python ${{ matrix.python-version }}) - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Cache pip - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }} - restore-keys: | - ${{ runner.os }}-pip- - - name: Install dependencies - run: | - set -xeu - python --version - pip install tox coverage - - name: Run tox targets for ${{ matrix.python-version }} - run: tox -e ${{ matrix.tox-env }} - - report-status: - name: success - runs-on: ubuntu-latest - needs: - - check-lint - - tests - steps: - - name: Report success - run: echo 'Success !' From efed35d9ea46b91bee0bea8e4e78657c899bcfcc Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Fri, 1 Jan 2021 21:05:36 +0000 Subject: [PATCH 371/447] Keep Calendra versioning distinct from Workalendar versioning. --- CHANGES.rst | 57 ++++++++++++++++------------------------------------- 1 file changed, 17 insertions(+), 40 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1d13da37..f64e3712 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,13 +1,10 @@ -v14.1.0 +v7.0.0 ------- Incorporate changes from workalendar v14.1.0 (2020-12-10) - Fix Russia 2021 holidays, thx @MichalChromcak for the bug report (#578). -v14.0.0 (unreleased) --------------------- - Incorporate changes from workalendar v14.0.0 (2020-11-27) - Fixes @@ -22,9 +19,6 @@ Incorporate changes from workalendar v14.0.0 (2020-11-27) - Added a `daterange` function in `workalendar.core` module to iterate between two dates. -v13.0.0 (unreleased) --------------------- - Incorporate changes from workalendar v13.0.0 (2020-11-13) - Calendars @@ -48,7 +42,7 @@ Incorporate changes from workalendar v13.0.0 (2020-11-13) - Use the `setup.cfg` file in the key to cache in `ci.yml` file (#587). - [OBSOLETE] Switched from bionic to focal on Travis CI (we've switched to GH actions after that). -v12.1.0 (2020-10-16) +Incorporate changes from workalendar v12.1.0 (2020-10-16) - New calendars @@ -66,15 +60,12 @@ v12.1.0 (2020-10-16) - Added a tox entrypoint to ensure code is Python 3.6+, using ``pyupgrade`` (#566). - Added the pyupgrade tox job to the test suite, amended contributing documentation (#566). -v12.0.0 (unreleased) --------------------- - Incorporate changes from workalendar v12.0.0 (2020-10-02) - **Deprecation:** Dropped support for Python 3.5. As of this version, workalendar now requires Python 3.6+ (#330). - Improve coverage of Singapore calendar (#546). -v11.0.1 (2020-09-11) +Incorporate changes from workalendar v11.0.1 (2020-09-11) - Add ISO code decorator to Catalonia calendar, thanks to @jbagot (#551). - Improve coverage of South Africa calendar (#546). @@ -82,9 +73,6 @@ v11.0.1 (2020-09-11) - Improve coverage of Canada (Nunavut) calendar (#546). - Improve coverage of Israel calendar (#546). -v11.0.0 (unreleased) --------------------- - Incorporate changes from workalendar v11.0.0 (2020-09-04) New calendar @@ -96,7 +84,7 @@ New feature - Added iCal export feature, initiated by @joooeey (#197). - Fix PRODID pattern for iCal exports: `"PRODID:-//workalendar//ical {__version__}//EN"`, using current workalendar version (#543). -v10.4.0 (2020-08-28) +Incorporate changes from workalendar v10.4.0 (2020-08-28) - New calendar @@ -113,7 +101,7 @@ v10.4.0 (2020-08-28) - Tech: Replace occurrences of `assertEquals` with `assertEqual` to clear warnings (#533). - Use `include_immaculate_conception` flag for Portugal, Brazil, Argentina, Paraguay calendars (#529). -v10.3.0 (2020-07-10) +Incorporate changes from workalendar v10.3.0 (2020-07-10) - Bugfixes @@ -126,12 +114,12 @@ v10.3.0 (2020-07-10) - Declaring the New year's Day as a worldwide holiday, with only two exceptions (to date): Israel & Qatar (#511). - Fixed `contributing.md` documentation with the new class/mixin organization (#511). -v10.2.0 (2020-06-26) +Incorporate changes from workalendar v10.2.0 (2020-06-26) - Bugfix: setting *Consciência Negra day* as a non-holiday by default for Brazilian calendars, thx to @edniemeyer (#516). - Bugfix: Introducing the changes in Croatia holidays as of 2020 - Remembrance Day, Independence Day, Statehood Day... thx to @davidpodrebarac for the bug report (#515). -v10.1.0 (2020-06-18) +Incorporate changes from workalendar v10.1.0 (2020-06-18) - Calendar fix @@ -142,35 +130,28 @@ v10.1.0 (2020-06-18) - Small fixes (docstrings, use of extends, etc) on Cayman Islands calendar (#507). - Moving Carnaval / Mardi Gras / Fat Tuesday calculation into the `workalendar.core` module, because it's used in at least 3 countries and some States / Counties in the USA. - -v10.0.0 (unreleased) --------------------- - Incorporate changes from workalendar v10.0.0 (2020-06-05) - **BREAKING CHANGE**: the ``IsoRegistry.get_calendar_class()`` method has been removed from the code and should no longer be used (#375, #495). -v9.2.0 (2020-06-02) +Incorporate changes from workalendar v9.2.0 (2020-06-02) - New Calendars - Added rules for all Switzerland Cantons, branching off the initial work by @brutasse (#497). -v9.0.1 (2020-05-22) +Incorporate changes from workalendar v9.0.1 (2020-05-22) - Making the Israel calendar more efficient (#498). - Fixing duplicated holidays in Hong-Kong and Hong-Kong Bank holiday calendars (#496). - Integrating Hong-Kong holidays for 2021 (#496). -v9.0.0 (unreleased) -------------------- - Incorporate changes from workalendar v9.0.0 (2020-04-24) - **BREAKING CHANGE**: the ``IsoRegistry.items()`` method has been removed from the API. You must use the ``get_calendars()`` to perform the same registry queries (#375, #491). - *Deprecation notice*: The usage of ``IsoRegistry.get_calendar_class()`` is strongly discouraged, in favor of ``get()``. The ``get_calendar_class`` method will be dropped in a further release. In the meantime, they'll be both equivalent (#375, #418). -v8.4.0 (2020-04-17) +Incorporate changes from workalendar v8.4.0 (2020-04-17) - New Calendar @@ -182,40 +163,37 @@ v8.4.0 (2020-04-17) - Fixed Malta calendar ; January 1st was already included, no need to add it to the ``FIXED_HOLIDAYS`` property (#469). - Small refactor in Netherlands calendar to use core constants (#470). -v8.3.0 (2020-04-14) +Incorporate changes from workalendar v8.3.0 (2020-04-14) - Fixing Hong-Kong calendar, where SAT are common working days (#477). - Introducing Hong-Kong Bank calendar. For banks, Saturdays are non-working days (#477). -v8.2.2 (2020-04-10) +Incorporate changes from workalendar v8.2.2 (2020-04-10) - Fixed Argentina's "Malvinas Day" date for 2020, shifted to March 31st because of the coronavirus crisis (#476). - Fixed Argentina's label for "Malvinas Day" and "Día de la Memoria" (#476). -v8.2.1 (2020-04-03) +Incorporate changes from workalendar v8.2.1 (2020-04-03) - Added BrazilBankCalendar to support `include_` flags and make it possible to extend and change these flags to support custom bank calendars (#474). -v8.2.0 (2020-03-13) +Incorporate changes from workalendar v8.2.0 (2020-03-13) - Added Belarus calendar, by @alexdoesstuff (#472). -v8.1.0 (2020-02-07) +Incorporate changes from workalendar v8.1.0 (2020-02-07) - Added Israel holidays eves and removed holidays which are not affecting the working days in Israel (#461). - Fix warning in China's holidays to dynamically read supported years, thx @fredrike (#459). -v8.0.2 (2020-01-24) +Incorporate changes from workalendar v8.0.2 (2020-01-24) - Fix several miscalculations in Georgia (USA) calendar (#451). -v8.0.1 (2020-01-24) +Incorporate changes from workalendar v8.0.1 (2020-01-24) - Fix Family Day for British Columbia (Canada) which was switched from 2nd to 3rd Monday of February in 2019 - thx @jbroudou for the bug report (#454). -v8.0.0 ------- - Incorporate changes from workalendar v8.0.0 (2020-01-10) - **BREAKING CHANGE** Drop Support for Python 2 - EOL January 1st 2020 (#442). @@ -264,7 +242,6 @@ Other changes - Increase Malaysia coverage by adding tests for missing Deepavali & Thaipusam. - Increase China coverage by adding tests for special extra-holidays & extra-working days cases. - v6.0.0 ------ From 39dec9ea184c69e52fc2e39bd2f59a95a5871844 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Jan 2021 16:13:24 -0500 Subject: [PATCH 372/447] Switch to markdown for readme --- README.md | 199 +++++++++++++++++++++++++++++++++++++++++++++ README.rst | 232 ----------------------------------------------------- setup.cfg | 3 +- 3 files changed, 201 insertions(+), 233 deletions(-) create mode 100644 README.md delete mode 100644 README.rst diff --git a/README.md b/README.md new file mode 100644 index 00000000..c493f27b --- /dev/null +++ b/README.md @@ -0,0 +1,199 @@ +[![](https://img.shields.io/pypi/v/calendra.svg)][1] + +[![](https://img.shields.io/pypi/pyversions/calendra.svg)][1] + + [1]: https://pypi.org/project/calendra + +[![Automated Tests](https://github.com/jaraco/calendra/workflows/Automated%20Tests/badge.svg)](https://github.com/jaraco/calendra/actions?query=workflow%3A%22Automated+Tests%22) + +[![](https://readthedocs.org/projects/calendra/badge/?version=latest)](https://calendra.readthedocs.io/en/latest/?badge=latest) + +# Overview + +Calendra is a Python module that offers classes able to handle calendars, +list legal / religious holidays and gives working-day-related computation +functions. + +# History + +Calendra is a fork of [Workalendar](https://github.com/peopledoc/workalendar) +designed to be more extensible and introspectable, adding interfaces where +[Workalendar is philosophically opposed for the sake of simplicity](https://github.com/peopledoc/workalendar/pull/79). + +What can Calendra do that Workalendar cannot? + +- Provides descriptions for holidays for the "day indicated" for each + Holiday (such as '3rd Monday in August'). +- Keeps distinct the indicated and observed dates for Holidays, such + that it's possible to determine on what day a given holiday is observed. +- Allows the number of Holidays in a calendar year to be counted. +- Consolidates observance logic in the core code rather than requiring + each calendar implementation to implement its own. + +# Status + +The project is stable and in production use. Calendra follows the principles +of [semver](https://semver.org) for released verisons. + +If you spot any bug or wish to add a calendar, please refer to the [Contributing doc](CONTRIBUTING.rst). + +# Usage sample + +```python-repl +>>> from datetime import date +>>> from calendra.europe import France +>>> cal = France() +>>> cal.holidays(2012) +[(datetime.date(2012, 1, 1), 'New year'), + (datetime.date(2012, 4, 9), 'Easter Monday'), + (datetime.date(2012, 5, 1), 'Labour Day'), + (datetime.date(2012, 5, 8), 'Victory in Europe Day'), + (datetime.date(2012, 5, 17), 'Ascension Day'), + (datetime.date(2012, 5, 28), 'Whit Monday'), + (datetime.date(2012, 7, 14), 'Bastille Day'), + (datetime.date(2012, 8, 15), 'Assumption of Mary to Heaven'), + (datetime.date(2012, 11, 1), "All Saints' Day"), + (datetime.date(2012, 11, 11), 'Armistice Day'), + (datetime.date(2012, 12, 25), 'Christmas')] +>>> cal.is_working_day(date(2012, 12, 25)) # it's Christmas +False +>>> cal.is_working_day(date(2012, 12, 30)) # it's Sunday +False +>>> cal.is_working_day(date(2012, 12, 26)) +True +>>> cal.add_working_days(date(2012, 12, 23), 5) # 5 working days after Xmas +datetime.date(2012, 12, 31) +``` + +For a more complete documentation and advanced usage, go to +[the official workalendar documentation](https://peopledoc.github.io/workalendar). + +# External dependencies + +Calendra has been tested on the Python versions declared in setup.cfg. + +If you're using wheels, you should be fine without having to install extra system packages. As of `v7.0.0`, we have dropped `ephem` as a dependency for computing astronomical ephemeris in favor of `skyfield`. So if you had any trouble because of this new dependency, during the installation or at runtime, [do not hesitate to file an issue](https://github.com/peopledoc/workalendar/issues/). + +# Tests + +To run test, just install tox with `pip install tox` and run `tox` +from the command line. + + +# Available Calendars + +## Europe + +* Austria +* Belgium +* Bulgaria +* Cayman Islands +* Croatia +* Cyprus +* Czech Republic +* Denmark +* Estonia +* European Central Bank +* Finland +* France +* France (Alsace / Moselle) +* Germany +* Greece +* Hungary +* Iceland +* Ireland +* Italy +* Latvia +* Lithuania +* Luxembourg +* Malta +* Netherlands +* Norway +* Poland +* Portugal +* Romania +* Russia +* Serbia +* Slovakia +* Slovenia +* Spain (incl. Catalonia) +* Sweden +* Switzerland + + * Vaud + * Geneva + +* Turkey +* Ukraine +* United Kingdom (incl. Northern Ireland, Scotland and all its territories) + +## America + +* Argentina +* Barbados +* Brazil (all states, cities and for bank transactions, except the city of Viana) +* Canada (including provincial and territory holidays) +* Chile +* Colombia +* Mexico +* Panama +* Paraguay +* United States of America + + * State holidays for all the 50 States + * American Samoa + * Chicago, Illinois + * Guam + * Suffolk County, Massachusetts + * California Education, Berkeley, San Francisco, West Hollywood + * Florida Legal and Florida Circuit Courts, Miami-Dade + +## Asia + +* China +* Hong Kong +* Israel +* Japan +* JapanBank +* Malaysia +* Qatar +* Singapore +* South Korea +* Taiwan + +## Oceania + +* Australia (incl. its different states) +* Marshall Islands +* New Zealand + +## Africa + +* Algeria +* Angola +* Benin +* Ivory Coast +* Madagascar +* São Tomé +* South Africa + +And more to come (I hope!) + +# Caveats + +Please take note that some calendars are not 100% accurate. The most common +example is the Islamic calendar, where some computed holidays are not exactly on +the same official day decided by religious authorities, and this may vary +country by country. Whenever it's possible, try to adjust your results with +the official data provided by the adequate authorities. + +# Contributing + +Please read our [CONTRIBUTING.rst](https://github.com/jaraco/calandra/blob/master/CONTRIBUTING.rst) +document to discover how you can contribute to `calendra`. Pull-requests +are very welcome. + +# License + +This library is published under the terms of the MIT License. Please check the +LICENSE file for more details. diff --git a/README.rst b/README.rst deleted file mode 100644 index 6a41f9d0..00000000 --- a/README.rst +++ /dev/null @@ -1,232 +0,0 @@ -.. image:: https://img.shields.io/pypi/v/calendra.svg - :target: `PyPI link`_ - -.. image:: https://img.shields.io/pypi/pyversions/calendra.svg - :target: `PyPI link`_ - -.. _PyPI link: https://pypi.org/project/calendra - -.. image:: https://github.com/jaraco/calendra/workflows/Automated%20Tests/badge.svg - :target: https://github.com/jaraco/calendra/actions?query=workflow%3A%22Automated+Tests%22 - :alt: Automated Tests - -.. .. image:: https://img.shields.io/badge/code%20style-black-000000.svg -.. :target: https://github.com/psf/black -.. :alt: Code style: Black - -.. image:: https://readthedocs.org/projects/calendra/badge/?version=latest - :target: https://calendra.readthedocs.io/en/latest/?badge=latest - -Overview -======== - -Calendra is a Python module that offers classes able to handle calendars, -list legal / religious holidays and gives working-day-related computation -functions. - -History -======= - -Calendra is a fork of `Workalendar `_ -designed to be more extensible and introspectable, adding interfaces where -`Workalendar is philosophically opposed for the sake of simplicity -`_. - -What can Calendra do that Workalendar cannot? - -- Provides descriptions for holidays for the "day indicated" for each - Holiday (such as '3rd Monday in August'). -- Keeps distinct the indicated and observed dates for Holidays, such - that it's possible to determine on what day a given holiday is observed. -- Allows the number of Holidays in a calendar year to be counted. -- Consolidates observance logic in the core code rather than requiring - each calendar implementation to implement its own. - -Status -====== - -The project is stable and in production use. Calendra follows the principles -of `semver `_ for released verisons. - -If you spot any bug or wish to add a calendar, please refer to the `Contributing doc `_. - -Usage sample -============ - -.. code-block:: python - - >>> from datetime import date - >>> from calendra.europe import France - >>> cal = France() - >>> cal.holidays(2012) - [(datetime.date(2012, 1, 1), 'New year'), - (datetime.date(2012, 4, 9), 'Easter Monday'), - (datetime.date(2012, 5, 1), 'Labour Day'), - (datetime.date(2012, 5, 8), 'Victory in Europe Day'), - (datetime.date(2012, 5, 17), 'Ascension Day'), - (datetime.date(2012, 5, 28), 'Whit Monday'), - (datetime.date(2012, 7, 14), 'Bastille Day'), - (datetime.date(2012, 8, 15), 'Assumption of Mary to Heaven'), - (datetime.date(2012, 11, 1), "All Saints' Day"), - (datetime.date(2012, 11, 11), 'Armistice Day'), - (datetime.date(2012, 12, 25), 'Christmas')] - >>> cal.is_working_day(date(2012, 12, 25)) # it's Christmas - False - >>> cal.is_working_day(date(2012, 12, 30)) # it's Sunday - False - >>> cal.is_working_day(date(2012, 12, 26)) - True - >>> cal.add_working_days(date(2012, 12, 23), 5) # 5 working days after Xmas - datetime.date(2012, 12, 31) - -For a more complete documentation and advanced usage, go to -`the official workalendar documentation `_. - -External dependencies -===================== - -Calendra has been tested on the Python versions declared in setup.cfg. - -If you're using wheels, you should be fine without having to install extra system packages. As of ``v7.0.0``, we have dropped ``ephem`` as a dependency for computing astronomical ephemeris in favor of ``skyfield``. So if you had any trouble because of this new dependency, during the installation or at runtime, `do not hesitate to file an issue `_. - -Tests -===== - -Travis status: - -.. image:: https://api.travis-ci.org/jaraco/calendra.png - - -To run test, just install tox with ``pip install tox`` and run:: - - tox - -from the command line. - - -Available Calendars -=================== - -Europe ------- - -* Austria -* Belgium -* Bulgaria -* Cayman Islands -* Croatia -* Cyprus -* Czech Republic -* Denmark -* Estonia -* European Central Bank -* Finland -* France -* France (Alsace / Moselle) -* Germany -* Greece -* Hungary -* Iceland -* Ireland -* Italy -* Latvia -* Lithuania -* Luxembourg -* Malta -* Netherlands -* Norway -* Poland -* Portugal -* Romania -* Russia -* Serbia -* Slovakia -* Slovenia -* Spain (incl. Catalonia) -* Sweden -* Switzerland - - * Vaud - * Geneva - -* Turkey -* Ukraine -* United Kingdom (incl. Northern Ireland, Scotland and all its territories) - -America -------- - -* Argentina -* Barbados -* Brazil (all states, cities and for bank transactions, except the city of Viana) -* Canada (including provincial and territory holidays) -* Chile -* Colombia -* Mexico -* Panama -* Paraguay -* United States of America - - * State holidays for all the 50 States - * American Samoa - * Chicago, Illinois - * Guam - * Suffolk County, Massachusetts - * California Education, Berkeley, San Francisco, West Hollywood - * Florida Legal and Florida Circuit Courts, Miami-Dade - -Asia ----- - -* China -* Hong Kong -* Israel -* Japan -* JapanBank -* Malaysia -* Qatar -* Singapore -* South Korea -* Taiwan - -Oceania -------- - -* Australia (incl. its different states) -* Marshall Islands -* New Zealand - -Africa ------- - -* Algeria -* Angola -* Benin -* Ivory Coast -* Madagascar -* São Tomé -* South Africa - -And more to come (I hope!) - -Caveats -======= - -Please take note that some calendars are not 100% accurate. The most common -example is the Islamic calendar, where some computed holidays are not exactly on -the same official day decided by religious authorities, and this may vary -country by country. Whenever it's possible, try to adjust your results with -the official data provided by the adequate authorities. - -Contributing -============ - -Please read our `CONTRIBUTING.rst `_ -document to discover how you can contribute to ``calendra``. Pull-requests -are very welcome. - -License -======= - -This library is published under the terms of the MIT License. Please check the -LICENSE file for more details. diff --git a/setup.cfg b/setup.cfg index c5383a5e..26de1425 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,7 +4,8 @@ name = calendra author = Jason R. Coombs author_email = jaraco@jaraco.com description = Worldwide holidays and working days helper and toolkit. -long_description = file:README.rst +long_description = file:README.md +long_description_content_type = text/markdown url = https://github.com/jaraco/calendra classifiers = Development Status :: 5 - Production/Stable From 2af71a16cfcab4405363c96a17f490118388fcf9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Jan 2021 18:16:42 -0500 Subject: [PATCH 373/447] Bump to pytest-checkdocs 2.2 to avoid failures on markdown. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 26de1425..e7e6e1dd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,7 +34,7 @@ setup_requires = setuptools_scm[toml] >= 3.4.1 testing = # upstream pytest >= 3.5, !=3.7.3 - pytest-checkdocs >= 1.2.3 + pytest-checkdocs >= 2.2 pytest-flake8 # disabled for easier merging # pytest-black >= 0.3.7; python_implementation != "PyPy" From 4ca824bb6baf3e1e498d95381d51427ba0654d72 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Jan 2021 18:54:43 -0500 Subject: [PATCH 374/447] Remove more RST files; defer to workalendar docs. --- CONTRIBUTING.rst | 284 ------------------------------------------ README.md | 2 +- docs/_config.yml | 3 - docs/advanced.md | 85 ------------- docs/basic.md | 112 ----------------- docs/class-options.md | 108 ---------------- docs/ical.md | 71 ----------- docs/index.md | 23 ---- docs/index.rst | 2 + docs/iso-registry.md | 153 ----------------------- 10 files changed, 3 insertions(+), 840 deletions(-) delete mode 100644 CONTRIBUTING.rst delete mode 100644 docs/_config.yml delete mode 100644 docs/advanced.md delete mode 100644 docs/basic.md delete mode 100644 docs/class-options.md delete mode 100644 docs/ical.md delete mode 100644 docs/index.md delete mode 100644 docs/iso-registry.md diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 9efa7780..00000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,284 +0,0 @@ -====================== -Contribute to Calendra -====================== - -Use it (and test it) -==================== - -If you are using ``calendra``, you are already contributing to it. As long -as you are able to check its result, compare the designated working days and -holidays to the reality, and make sure these are right, you're helping. - -If any of the computed holidays for the country / area your are using is -**wrong**, please report -`it using the Github issues `_. - -Report an issue -=============== - -If you think you've found a bug you can report an issue. In order to help us -sort this out, please follow the guidelines: - -* Tell us which ``calendra`` version (master, PyPI release) you are using. -* Tell us which Python version you are using, and your platform. -* Give us extensive details on the country / area Calendar, the exact date(s) that was (were) computed and the one(s) that should have been the correct result. -* If possible, please provide us a reliable source about the designated country / area calendar, where we could effectively check that we were wrong about a date, and giving us a way to patch our code properly so we can fix the bug. - - -Adding new calendars -==================== - -Since ``calendra`` is mainly built around configuration variables and generic -methods, it's not that difficult to add a calendar to our codebase. A few -**mandatory** steps should be observed: - -1. Fork the repository and create a new branch named after the calendar you want to implement, -2. Add a test class to the test suite that checks holidays, -3. Implement the class using the core class APIs as much as possible. Test it until all tests pass. -4. Make a nice pull-request we'll be glad to review and merge when it's perfect. - -.. note:: - - Please respect the PEP8 convention, otherwise your PR won't be accepted. - -Example -------- - -Let's assume you want to include the holidays of the magic (fictional) kingdom -of *"Zhraa"*, which has a few holidays of different kind. - -For the sake of the example, it has the following specs: - -* it's a Gregorian-based Calendar (i.e. the Western European / American one), -* even if the King is not versed into religions, the kingdom includes a few Christian holidays, -* even if you never knew about it, it is set in Europe, - -Here is a list of the holidays in *Zhraa*: - -* January 1st, New year's Day, -* May 1st, Labour day, -* Easter Monday, which is variable (from March to May), -* The first monday in June, to celebrate the birth of the Founder of the Kingdom, Zhraa (nobody knows the exact day he was born, so this day was chosen as a convention), -* The birthday of the King, August 2nd. -* Christmas Day, Dec 25th. - - -Getting ready -############# - -You'll need to install ``calendra`` dependencies beforehand. What's great is -that you'll use virtualenv to set it up. Or even better: ``virtualenvwrapper``. -Just go in your working copy (cloned from github) of calendra and type, for -example:: - - mkvirtualenv CALENDRA - pip install -e ./ - - -Test-driven start -################# - - -Let's prepare the Zhraa class. Edit the ``calendra/europe/zhraa.py`` file and -add a class like this:: - - from calendra.core import WesternCalendar - - class Zhraa(WesternCalendar): - "Kingdom of Zhraa" - -.. note:: - - The docstring is not mandatory, but if you omit it, the ``name`` property of - your class will be the name of your class. For example, using upper - CamelCase, ``KingdomOfZhraa``. For a more human-readable label, use your - docstring. - -Meanwhile, in the ``calendra/europe/__init__.py`` file, add these snippets -where needed: - -.. code-block:: python - - from .zhraa import Zhraa - # ... - __all__ = ( - Belgium, - CzechRepublic, - # ... - Zhraa, - ) - -Now, we're building a test class. Edit the ``calendra/tests/test_europe.py`` -file and add the following code:: - - from ..europe import Zhraa - # snip... - - class ZhraaTest(GenericCalendarTest): - cal_class = Zhraa - - def test_year_2014(self): - holidays = self.cal.holidays_set(2014) - self.assertIn(date(2014, 1, 1), holidays) # new year - self.assertIn(date(2014, 5, 1), holidays) # labour day - self.assertIn(date(2014, 8, 2), holidays) # king birthday - self.assertIn(date(2014, 12, 25), holidays) # Xmas - # variable days - self.assertIn(date(2014, 4, 21), holidays) # easter monday - self.assertIn(date(2014, 6, 2), holidays) # First MON in June - -of course, if you run the test using the ``tox`` or ``py.test`` command, -this will fail, since we haven't implemented anything yet. - -Install tox using the following command:: - - workon CALENDRA - pip install tox - -With the ``WesternCalendar`` base class you have at least one holiday as a -bonus: the New year's day, which is commonly a holiday. - -Add fixed days -############## - -:: - - class Zhraa(WesternCalendar): - FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( - (5, 1, "Labour Day"), - (8, 2, "King Birthday"), - ) - -Now we've got 3 holidays out of 6. - -Add religious holidays -###################### - -Using ChristianMixin as a base to our Zhraa class will instantly add Christmas -Day as a holiday. Now we can add Easter monday just by triggering the correct -flag. - -:: - - from calendra.core import WesternCalendar, ChristianMixin - - class Zhraa(WesternCalendar, ChristianMixin): - include_easter_monday = True - FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( - (5, 1, "Labour Day"), - (8, 2, "King Birthday"), - ) - -Almost there, 5 holidays out of 6. - -Add variable "non-usual" holidays -################################# - -There are many static methods that will grant you a clean access to variable -days computation. It's very easy to add days like the "Birthday of the Founder":: - - - class Zhraa(WesternCalendar, ChristianMixin): - include_easter_monday = True - FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( - (5, 1, "Labour Day"), - (8, 2, "King Birthday"), - ) - - def get_variable_days(self, year): - # usual variable days - days = super(Zhraa, self).get_variable_days(year) - - days.append( - (Zhraa.get_nth_weekday_in_month(year, 6, MON), - 'Day of the Founder'), - ) - return days - -.. note:: - - Please mind that the returned "variable_days" is a list of tuples. The first - item being a date object (in the Python ``datetime.date`` sense) and the - second one is the label string. - -Add you calendar to the global registry -####################################### - -If you're adding a Country calendar that has an ISO code, you may want to add -it to our global registry. - -Workalendar is providing a registry that you can use to query and fetch calendar -based on their ISO code. For the current example, let's pretend that the Zhraa -Kingdom ISO code is ``ZK``. - -To register, add the following:: - - from workalendar.registry import iso_register - - @iso_register('ZK') - class Zhraa(WesternCalendar, ChristianMixin): - # The rest of your code... - -You're done for the code! -######################### - -There you are. Commit with a nice commit message, test, make sure it works for -the other years as well and you're almost there. - -The final steps -############### - -Do not forget to: - -1. put the appropriate doctring in the Calendar class. -2. add your calendar in the ``README.rst`` file, included in the appropriate continent. -3. add your calendar to the ``CHANGELOG`` file. - -.. note:: - - We're planning to build a complete documentation for the other cases - (special holiday rules, other calendar types, other religions, etc). But - with this tutorial you're sorted for a lot of other calendars. - - -Other code contributions -======================== - -There are dozens of calendars all over the world. We'd appreciate you to -contribute to the core of the library by adding some new Mixins or Calendars. - -Bear in mind that the code you'd provide **must** be tested using unittests -before you submit your pull-request. - -Syncing from upstream Workalendar -================================= - -1. Create a fork, and clone it:: - - git clone git@github.com:ShaheedHaque/calendra.git - -2. Add a remote pointing to upstream Workalendar:: - - cd calendra - git remote add workalendar git@github.com:peopledoc/workalendar.git - -3. Pull the remote, including its tags:: - - git remote update - -4. Make a branch to work on:: - - git checkout -b srh_sync_workalendar_5_2_2 - -5. Review the changes to be brought in:: - - git diff 5.2.2..HEAD - -6. Merge, based on the diff command above, and a careful review/tweaking - of the results. - -7. Proceed with care, and test as needed:: - - tox - -8. Generate a PR when ready! diff --git a/README.md b/README.md index aab7a06d..d159fc84 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ What can Calendra do that Workalendar cannot? The project is stable and in production use. Calendra follows the principles of [semver](https://semver.org) for released verisons. -If you spot any bug or wish to add a calendar, please refer to the [Contributing doc](docs/contributing.md). +If you spot any bug or wish to add a calendar, please refer to the [Contributing doc](https://peopledoc.github.io/workalendar/contributing.html). ## Usage sample diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 6952c416..00000000 --- a/docs/_config.yml +++ /dev/null @@ -1,3 +0,0 @@ -theme: jekyll-theme-hacker -include: - - contributing.md diff --git a/docs/advanced.md b/docs/advanced.md deleted file mode 100644 index afba0b44..00000000 --- a/docs/advanced.md +++ /dev/null @@ -1,85 +0,0 @@ -# Advanced usage - -[Home](index.md) / [Basic usage](basic.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) - -The following examples will better suit people willing to contribute to Calendra or building their custom calendars. They use primitives and methods attached to the core Calendar class to enable computation of complex holidays, that is to say dates that are not fixed, not related to religious calendars (Christmas always happens on December 25th, right?). - -## Find the following working day after a date - -It's a bit like a shortcut function to find the next working day after the given date. - -Please note that if the day you're entering is already a working day, that'll be the returned result. - -```python ->>> from datetime import date, datetime ->>> from calendra.europe import France ->>> cal = France() ->>> cal.find_following_working_day(date(2018, 7, 6)) # It's FRI -datetime.date(2018, 7, 6) ->>> cal.find_following_working_day(date(2018, 7, 7)) # SAT => next MON -datetime.date(2018, 7, 9) -``` - -**WARNING**: this function doesn't take into account the existing holidays in the calendar. If you need this, use the ``add_working_days()`` function as described in the [Basic usage document](basic.md). - -## Find the 4th Thursday in November - -That's a puzzling question that we needed to address when we had to implement United States of America holidays calendars. Thanksgiving day, for example, which is on the 4th Thursday in November (Thanksgiving Friday is the day after this thursday)... and many others, are defined as: - -> the ``nth`` ``Weekday name`` of the month of ``Month name`` - -Or even better for Election day, which is: - -> the Tuesday next after the first Monday in the month of November. - -We've got you covered with static methods in the core ``Calendar`` class. - -```python ->>> from calendra.core import Calendar, THU ->>> Calendar.get_nth_weekday_in_month(2018, 11, THU) # by default, find the first -datetime.date(2018, 11, 2) ->>> Calendar.get_nth_weekday_in_month(2018, 11, THU, 4) # Thanksgiving -datetime.date(2018, 11, 23) -``` - -If you want to find the 2nd Monday after the 4th of July, you can use the ``start`` argument: - -```python ->>> from datetime import date ->>> from calendra.core import Calendar, MON ->>> Calendar.get_nth_weekday_in_month( - 2018, # Year - 7, # Month - MON, # Monday - 2, # The 2nd one - start=date(2018, 7, 9)) -datetime.date(2018, 7, 16) -``` - -## Find the last Monday in the Month - -This one was a bit trickier, because it may happen that you have 4 or 5 `weekday name` in the same month. e.g: during the month of July 2018, you have 5 Mondays (2nd, 9th, 16th, 23rd, 30th), but only 4 Saturdays (7th, 14th, 21st, 28th). - -Same as above, there's a static method in the ``Calendar`` class: - -```python ->>> from calendra.core import Calendar, MON, FRI ->>> Calendar.get_last_weekday_in_month(2018, 7, MON) -datetime.date(2018, 7, 30) ->>> Calendar.get_last_weekday_in_month(2018, 7, FRI) -datetime.date(2018, 7, 27) -``` - -## Find the first Monday after a given date - -Colombia, as an example, states that the Epiphany Day is set to the "First Monday after the 6th of January". You may use the following function to find this specific formula: - -```python ->>> from calendra.core import Calendar, MON ->>> from datetime import date ->>> jan_6th = date(2018, 1, 6) # It's a Saturday ->>> Calendar.get_first_weekday_after(jan_6th, MON) -datetime.date(2018, 1, 8) -``` - -[Home](index.md) / [Basic usage](basic.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) diff --git a/docs/basic.md b/docs/basic.md deleted file mode 100644 index 262ce169..00000000 --- a/docs/basic.md +++ /dev/null @@ -1,112 +0,0 @@ -# Basic usage - -[Home](index.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) - -Here are basic examples of what Calendra can do for you. As an integrator or a simple Calendra user, you will use these methods to retrieve calendars, and get basic outputs for a given date. - -## Get holidays for a given country and year - -```python ->>> from calendra.europe import France ->>> cal = France() ->>> cal.holidays(2012) -[(datetime.date(2012, 1, 1), 'New year'), - (datetime.date(2012, 4, 9), 'Easter Monday'), - (datetime.date(2012, 5, 1), 'Labour Day'), - (datetime.date(2012, 5, 8), 'Victory in Europe Day'), - (datetime.date(2012, 5, 17), 'Ascension Day'), - (datetime.date(2012, 5, 28), 'Whit Monday'), - (datetime.date(2012, 7, 14), 'Bastille Day'), - (datetime.date(2012, 8, 15), 'Assumption of Mary to Heaven'), - (datetime.date(2012, 11, 1), "All Saints' Day"), - (datetime.date(2012, 11, 11), 'Armistice Day'), - (datetime.date(2012, 12, 25), 'Christmas')] -``` - -As you can see, the output is a simple list of tuples, with the first member being a `datetime.date` object, and the second one is the holiday label as a string. By design, we have chosen to stick to basic types so you can use them in your application and eventually compose your personal classes using them. - -## Know if a day is a holiday or not - -```python ->>> from datetime import date ->>> from calendra.europe import France ->>> cal = France() ->>> cal.is_working_day(date(2012, 12, 25)) # it's Christmas -False ->>> cal.is_working_day(date(2012, 12, 30)) # it's a Sunday -False ->>> cal.is_working_day(date(2012, 12, 26)) # No Boxing day in France -True -``` - -## Compute the "nth" working day after a given date - -That was the basic use-case that started this library development: to answer to the following question: - -> This task is due in 5 working days, starting of today. Can we calculate that? - -In the following example, we want to calculate 5 working days after December the 23rd 2012. - -| Date | Weekday | Working Day? | Count | -|:-----|:-------:|:------------:|:-----:| -| 23rd | SUN | No | 0 | -| 24th | MON | Yes | +1 | -| 25th | TUE | No (XMas) | - | -| 26th | WED | Yes | +2 | -| 27th | THU | Yes | +3 | -| 28th | FRI | Yes | +4 | -| 29th | SAT | No (weekend) | - | -| 30th | SUN | No (weekend) | - | -| 31th | MON | Yes | +5 | - -```python ->>> from datetime import date ->>> from calendra.europe import France ->>> cal = France() ->>> cal.add_working_days(date(2012, 12, 23), 5) -datetime.date(2012, 12, 31) -``` - -If we had requested the 6th day after the 23rd, it would have been January 2nd, 2013, because January 1st is New Year's Day. - -## Calculate the number or working days between two given days - -Let's say you want to know how many working days there are between April 2nd and June 17th of 2018, in France. Use the following: - -```python ->>> from datetime import date ->>> from calendra.europe import France ->>> cal = France() ->>> cal.get_working_days_delta(date(2018, 4, 2), date(2018, 6, 17)) -50 -``` - -## Standard date(time) types only, please! - -For your convenience, we allow both `datetime.date` and `datetime.datetime` types (and their subclasses) when using the core functions. - -**WARNING**: We'll only allow "dates" types coming from the Python standard library. If you're manipulating types from external library. Trying to pass a non-standard argument will result in raising a ``UnsupportedDateType`` error. - -Example: - -```python ->>> from datetime import date, datetime ->>> from calendra.europe import France ->>> cal = France() ->>> cal.is_working_day(datetime(2012, 12, 25, 14, 0, 39)) -False ->>> cal.add_working_days(datetime(2012, 12, 23, 14, 0, 39), 5) -datetime.datetime(2012, 12, 31) -``` - -If you really need it, you can use the ``add_working_days()`` with an option that will keep the ``datetime`` type: - -```python ->>> from datetime import date, datetime ->>> from calendra.europe import France ->>> cal = France() ->>> cal.add_working_days(datetime(2012, 12, 23, 14, 0, 39), 5, keep_datetime=True) -datetime.datetime(2012, 12, 31, 14, 0, 39) -``` - -[Home](index.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) diff --git a/docs/class-options.md b/docs/class-options.md deleted file mode 100644 index c2dddb20..00000000 --- a/docs/class-options.md +++ /dev/null @@ -1,108 +0,0 @@ -# Advanced feature: class options - -[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) - - -As of `v13.0.0` you can define *options* for your calendar. - -## Options as "flags" - -Let's consider the following calendar class: - -```python -@iso_register('ZK') -class Zhraa(WesternCalendar): - include_easter_monday = True - include_labour_day = True - FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( - (8, 2, "King Birthday"), - ) - - def get_variable_days(self, year): - # usual variable days - days = super().get_variable_days(year) - - days.append( - (Zhraa.get_nth_weekday_in_month(year, 6, MON), - 'Day of the Founder'), - ) - return days -``` - -In our example, we'll add a special holiday. It's **not** an official holiday, but it **can** be taken as a non-working day, if your organization or your administration decides it becomes one. - -For the sake of our use-case, let's say that the Zhraa Kingdom decides that January the 2nd can be considered as a day off in some special cases, for banks or schools, for example. Or at the discretion of your company. - -As an integrator, if you need to implement this behaviour, you can decide to have a separate class which includes your extra day. This would fit your needs. - -But what if your special organization decides to keep this day as a non-working day depending on a variable context? Let's say that it does not happen every year, or it's not in every city of the Kingdom, or not for all of your employees, etc. Using a dedicated class won't help you there. -But **optional arguments** surely will! - -Here's a slightly different version of our `Zhraa` class: - - -```python -@iso_register('ZK') -class Zhraa(WesternCalendar): - include_easter_monday = True - include_labour_day = True - FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( - (8, 2, "King Birthday"), - ) - - def __init__(self, include_january_2nd=False, **kwargs): - super().__init__(**kwargs) - self.include_january_2nd = include_january_2nd - - def get_variable_days(self, year): - # usual variable days - days = super().get_variable_days(year) - - days.append( - (Zhraa.get_nth_weekday_in_month(year, 6, MON), - 'Day of the Founder'), - ) - - if self.include_january_2nd: - days.append( - (date(year, 1, 2), "January 2nd") - ) - return days -``` - -In your *business-logic code*, you could then write: - -```python -include_january_2nd = year == 2019 \ - and (employee_name == "Bob" or employee_location == "North") -calendar = Zhraa(include_january_2nd=include_january_2nd) -``` - -As a consequence, depending on the context required by your business-logic process, you can include or exclude January 2nd as a holiday. - -## More than "flags" - -Of course, **you are not limited to booleans** to activate/deactivate a holiday when you want to create options. Use strings, numbers, objects, or whatever suits your needs. - -Here are some examples: - -* A ``region`` argument: in some regions, it defines new holidays, exceptions on including or excluding other days, etc. -* A `number_of_days_after_new_year` as a variable number of days that are to be accounted as non-working days after New Year's day. -* A ``day_of_the_founder_label`` string variable to easily customize the *Day of the Founder* label, according to your liking. -* ... sky is the limit! - -## Caveats - -### Why not using derivative classes? - -Again, for each of these options, you could define an inherited class of `Zhraa`. But as you have more and more option values and exceptions to your rules, you'd have to define a new class for each of these combinations. - -If you only have a couple of exceptions to your rules, you may prefer ot have a dedicated class. If the number of combinations goes over a limit, opt for *options*. - -### ERR_TOO_MANY_OPTIONS - -**Beware!** Options are nice and will help you in many cases, but it's probably a bad idea to go on and add dozens of them to your class constructor. As a consequence, your runtime code would be more and more complex. You'd have hard time covering all of the combinations around their values and test them. - -As in many other cases, your mileage may vary, but I doubt that you want to combine more than 5 of them. - -[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) diff --git a/docs/ical.md b/docs/ical.md deleted file mode 100644 index 0037a90a..00000000 --- a/docs/ical.md +++ /dev/null @@ -1,71 +0,0 @@ -# iCal export - -[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [Contributing](contributing.md) - -As of v11.0.0, you can export holidays generated by any Workalendar class to the iCal file format. - -## Export and retrieve the content - -In this example, we export all the public holidays for France from the year 2019 to the year 2022 (inclusive). - -```python ->>> from workalendar.europe import France ->>> cal = France() ->>> ical_content = cal.export_to_ical(period=[2019, 2022]) ->>> print(ical_content) -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//workalendar//ical 9.0.0//EN -BEGIN:VEVENT -SUMMARY:New year -DTSTART;VALUE=DATE:20190101 -DTSTAMP;VALUE=DATE-TIME:20200828T152009Z -UID:2019-01-01New year@peopledoc.github.io/workalendar -END:VEVENT -BEGIN:VEVENT -SUMMARY:Easter Monday -DTSTART;VALUE=DATE:20190422 -DTSTAMP;VALUE=DATE-TIME:20200828T152009Z -UID:2019-04-22Easter Monday@peopledoc.github.io/workalendar -END:VEVENT -BEGIN:VEVENT -SUMMARY:Labour Day -DTSTART;VALUE=DATE:20190501 -DTSTAMP;VALUE=DATE-TIME:20200828T152009Z -UID:2019-05-01Labour Day@peopledoc.github.io/workalendar -END:VEVENT -# ... -``` - -You may store this content into a file, or do anything else you want with it. - -If you don't provide any period, it defaults to [2000, 2030]. - -## Export and save it to a file - -You can also directly save your ical export to a file. The following example writes a ``france.ics`` file to your disk, with the holidays between 2019 and 2022. - -```python ->>> from workalendar.europe import France ->>> cal = France() ->>> cal.export_to_ical(period=[2019, 2022], target_path="france.ics") -``` - -**Note:** Please note that the export function won't work if the path is unwriteable or if it points at an existing directory. - -### Known file extensions - -The `target_path` argument may be provided with or without a file extension. For example, it can be either: - -| `target_path` argument | filename generated | -|:-----------------------|:-------------------| -| `france` | `france.ics` | -| `france.ics` | `france.ics` | -| `france.ical` | `france.ical` | -| `france.ifb` | `france.ifb` | -| `france.icalendar` | `france.icalendar` | -| `france.txt` | `france.txt.ics` | - -As you see, we only add the `.ics` extension if the current `target_path` extension is not known. - -[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [Contributing](contributing.md) diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 50917e45..00000000 --- a/docs/index.md +++ /dev/null @@ -1,23 +0,0 @@ -# Calendra documentation - -## Overview - -Calendra is a Python module that offers classes able to handle calendars, list legal / religious holidays and gives working-day-related computation functions. - -## Status - -This library is ready for production, although we may warn eventual users: some calendars may not be up-to-date, and this library doesn't cover all the existing countries on earth (yet). - -## Available Calendars - -See [the repository README](https://github.com/jaraco/calendra#available-calendars) for the most up-to-date catalog of the available calendars. - -## Usage examples - -* [Basic usage](basic.md) -* [Advanced usage](advanced.md) -* Advanced features: - * [Class options](class-options.md) - * [ISO Registry](iso-registry.md) - * [iCal Export](ical.md) -* [How to contribute](contributing.md) diff --git a/docs/index.rst b/docs/index.rst index 9645ff5a..bfbd592f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,6 +6,8 @@ Welcome to calendra documentation! history +See the `workalendar docs `_ +for more details. .. automodule:: calendra :members: diff --git a/docs/iso-registry.md b/docs/iso-registry.md deleted file mode 100644 index 64b30300..00000000 --- a/docs/iso-registry.md +++ /dev/null @@ -1,153 +0,0 @@ -# The ISO registry - -[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [iCal Export](ical.md) / [Contributing](contributing.md) - -As of version 3.0 (August/September 2018), we have introduced a global calendar registry for calendars related to a country or a region that belongs to the [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) or the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) for sub-regions (such as USA states, Australian territories or Canadian provinces and such). - -## Iterate over the whole registry - -```python ->>> from workalendar.registry import registry ->>> calendars = registry.get_calendars() # This returns a dictionary ->>> for code, calendar_class in calendars.items(): -... print("`{}` is code for '{}'".format(code, calendar_class.name)) -`AT` is code for 'Austria' -`BE` is code for 'Belgium' -`BG` is code for 'Bulgaria' -`HR` is code for 'Croatia' -`CY` is code for 'Cyprus' -`CZ` is code for 'Czech Republic' -`EE` is code for 'Estonia' -`DK` is code for 'Denmark' -`FI` is code for 'Finland' -`FR` is code for 'France' -... continued -``` - -The "private property" `registry.region_registry` is a `dict` object, with the ISO code as a key, and the calendar class as the value. As a "workalendar standard", **every** calendar in the registry has a `name` property (derived from the docstring), so you'd probably be able to build a user-friendly list of available calendars, for a dropdown list, for example. - -**DEPRECATION WARNING**: As of version 9.0.0, the ``IsoRegistry.items()`` has been renamed into ``IsoRegistry.get_calendars()`` for all your queries in the registry. - -## Retrieve a collection of regions - -If you want the full dictionary of **countries**, you can use the ``get_calendars()`` method. - -```python ->>> registry.get_calendars() -{'AT': , - 'BE': , - 'BG': , - 'KY': , - # ... continued -} -``` - -Let's say you'd need only a subset of the ISO registry. For example, France, Switzerland and Canada calendars. You can use the method `get_calendars()` to filter only the calendars you want. - -```python ->>> registry.get_calendars(['FR', 'CH', 'CA']) -{'FR': , - 'CH': , - 'CA': } -``` - -Also, if you want those regions **and** their subregions, you can use the `include_subregions` flag: - -```python ->>> registry.get_calendars(['FR', 'CH', 'CA'], include_subregions=True) -{'FR': workalendar.europe.france.France, - 'CH': workalendar.europe.switzerland.Switzerland, - 'CH-AG': workalendar.europe.switzerland.Aargau, - 'CH-AI': workalendar.europe.switzerland.AppenzellInnerrhoden, - 'CH-AR': workalendar.europe.switzerland.AppenzellAusserrhoden, - 'CH-BE': workalendar.europe.switzerland.Bern, - 'CH-BL': workalendar.europe.switzerland.BaselLandschaft, - 'CH-BS': workalendar.europe.switzerland.BaselStadt, - 'CH-FR': workalendar.europe.switzerland.Fribourg, - 'CH-GE': workalendar.europe.switzerland.Geneva, - 'CH-GL': workalendar.europe.switzerland.Glarus, - 'CH-GR': workalendar.europe.switzerland.Graubunden, - 'CH-JU': workalendar.europe.switzerland.Jura, - 'CH-LU': workalendar.europe.switzerland.Luzern, - 'CH-NE': workalendar.europe.switzerland.Neuchatel, - 'CH-NW': workalendar.europe.switzerland.Nidwalden, - 'CH-OW': workalendar.europe.switzerland.Obwalden, - 'CH-SG': workalendar.europe.switzerland.StGallen, - 'CH-SH': workalendar.europe.switzerland.Schaffhausen, - 'CH-SO': workalendar.europe.switzerland.Solothurn, - 'CH-SZ': workalendar.europe.switzerland.Schwyz, - 'CH-TG': workalendar.europe.switzerland.Thurgau, - 'CH-TI': workalendar.europe.switzerland.Ticino, - 'CH-UR': workalendar.europe.switzerland.Uri, - 'CH-VD': workalendar.europe.switzerland.Vaud, - 'CH-VS': workalendar.europe.switzerland.Valais, - 'CH-ZG': workalendar.europe.switzerland.Zug, - 'CH-ZH': workalendar.europe.switzerland.Zurich, - 'CA': workalendar.america.canada.Canada, - 'CA-ON': workalendar.america.canada.Ontario, - 'CA-QC': workalendar.america.canada.Quebec, - 'CA-BC': workalendar.america.canada.BritishColumbia, - 'CA-AB': workalendar.america.canada.Alberta, - 'CA-SK': workalendar.america.canada.Saskatchewan, - 'CA-MB': workalendar.america.canada.Manitoba, - 'CA-NB': workalendar.america.canada.NewBrunswick, - 'CA-NS': workalendar.america.canada.NovaScotia, - 'CA-PE': workalendar.america.canada.PrinceEdwardIsland, - 'CA-NL': workalendar.america.canada.Newfoundland, - 'CA-YT': workalendar.america.canada.Yukon, - 'CA-NT': workalendar.america.canada.NorthwestTerritories, - 'CA-NU': workalendar.america.canada.Nunavut} -``` - -*Note*: if any of the codes is unknown, this function won't raise an error. - -You can also get the full dict of all calendars registered in the ISO Registry with all the subregions using the following function call: - -```python ->>> registry.get_calendars(include_subregions=True) -``` - -## Select only one calendar - -Let's say that we only know the ISO code for Switzerland (`CH`). If we want to compute holidays for Switzerland in 2018, we can do as follows: - -```python ->>> CalendarClass = registry.get('CH') ->>> calendar = CalendarClass() ->>> calendar.holidays(2018) -[(datetime.date(2018, 1, 1), 'New year'), - (datetime.date(2018, 3, 30), 'Good Friday'), - (datetime.date(2018, 4, 1), 'Easter Sunday'), - (datetime.date(2018, 4, 2), 'Easter Monday'), - (datetime.date(2018, 5, 10), 'Ascension Thursday'), - (datetime.date(2018, 5, 20), 'Whit Sunday'), - (datetime.date(2018, 5, 21), 'Whit Monday'), - (datetime.date(2018, 8, 1), 'National Holiday'), - (datetime.date(2018, 12, 25), 'Christmas Day'), - (datetime.date(2018, 12, 26), 'Boxing Day')] -``` - -*Note*: this function would return `None` if the code is unknown. - -**DEPRECATION WARNING**: As of version 10.0.0, the ``IsoRegistry.get_calendar_class()`` has been renamed into ``IsoRegistry.get()`` to retrieve a single calendar class out of the registry. - - -## Select only sub-regions - -As an example, the United States of America, or Australia and others are divided into "sub-regions" (states, provinces, territories, etc). There are usually Federal or State holidays, and variants depending on the region you're targeting. - -```python ->>> registry.get_subregions('AU') -{'AU-ACT': , - 'AU-NSW': , - 'AU-NT': , - 'AU-QLD': , - 'AU-SA': , - 'AU-TAS': , - 'AU-VIC': , - 'AU-WA': } -``` - -*Note*: this function will return an empty `dict` if the code is unknown. - -[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [iCal Export](ical.md) / [Contributing](contributing.md) From fb20e97c29ec24ba7d38fa4ff15d7bd391c50122 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Jan 2021 19:50:54 -0500 Subject: [PATCH 375/447] Mark failing test as expected to fail. --- calendra/tests/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/calendra/tests/__init__.py b/calendra/tests/__init__.py index 1490095a..fa2f0c99 100644 --- a/calendra/tests/__init__.py +++ b/calendra/tests/__init__.py @@ -6,6 +6,7 @@ from unittest import TestCase from freezegun import freeze_time +import pytest from ..core import Calendar from .. import __version__ @@ -47,6 +48,9 @@ def test_january_1st(self): else: self.assertNotIn(date(self.year, 1, 1), holidays) + @pytest.mark.xfail( + "platform.system() == 'Windows'", + reason="https://github.com/peopledoc/workalendar/issues/607") def test_ical_export(self): """Check that an iCal file can be created according to iCal spec.""" class_name = self.cal_class.__name__ From 4dda30a4cc30fc666a69f7b8c8cc7930669e4763 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 1 Jan 2021 20:14:09 -0500 Subject: [PATCH 376/447] Restore changelog for 6.1 line --- CHANGES.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f64e3712..439a6045 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -194,6 +194,20 @@ Incorporate changes from workalendar v8.0.1 (2020-01-24) - Fix Family Day for British Columbia (Canada) which was switched from 2nd to 3rd Monday of February in 2019 - thx @jbroudou for the bug report (#454). +v6.1.2 +------ + +#14: Replaced implicit dependency on setuptools with explicit +dependency on importlib.metadata. + +v6.1.1 +------ + +Fix version inference when installed from sdist. + +v6.1.0 +------ + Incorporate changes from workalendar v8.0.0 (2020-01-10) - **BREAKING CHANGE** Drop Support for Python 2 - EOL January 1st 2020 (#442). From 4d877644cfa228ce39957e8b93db5e64049ea967 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 3 Jan 2021 19:44:17 -0500 Subject: [PATCH 377/447] Restore markdown docs removed in 8f54646168a6e8e665557b93a20661d0ec7eb623. They're not causing any harm. --- docs/advanced.md | 85 +++++++++++++++++++++++ docs/basic.md | 112 +++++++++++++++++++++++++++++++ docs/class-options.md | 108 +++++++++++++++++++++++++++++ docs/ical.md | 71 ++++++++++++++++++++ docs/index.md | 23 +++++++ docs/iso-registry.md | 153 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 552 insertions(+) create mode 100644 docs/advanced.md create mode 100644 docs/basic.md create mode 100644 docs/class-options.md create mode 100644 docs/ical.md create mode 100644 docs/index.md create mode 100644 docs/iso-registry.md diff --git a/docs/advanced.md b/docs/advanced.md new file mode 100644 index 00000000..afba0b44 --- /dev/null +++ b/docs/advanced.md @@ -0,0 +1,85 @@ +# Advanced usage + +[Home](index.md) / [Basic usage](basic.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) + +The following examples will better suit people willing to contribute to Calendra or building their custom calendars. They use primitives and methods attached to the core Calendar class to enable computation of complex holidays, that is to say dates that are not fixed, not related to religious calendars (Christmas always happens on December 25th, right?). + +## Find the following working day after a date + +It's a bit like a shortcut function to find the next working day after the given date. + +Please note that if the day you're entering is already a working day, that'll be the returned result. + +```python +>>> from datetime import date, datetime +>>> from calendra.europe import France +>>> cal = France() +>>> cal.find_following_working_day(date(2018, 7, 6)) # It's FRI +datetime.date(2018, 7, 6) +>>> cal.find_following_working_day(date(2018, 7, 7)) # SAT => next MON +datetime.date(2018, 7, 9) +``` + +**WARNING**: this function doesn't take into account the existing holidays in the calendar. If you need this, use the ``add_working_days()`` function as described in the [Basic usage document](basic.md). + +## Find the 4th Thursday in November + +That's a puzzling question that we needed to address when we had to implement United States of America holidays calendars. Thanksgiving day, for example, which is on the 4th Thursday in November (Thanksgiving Friday is the day after this thursday)... and many others, are defined as: + +> the ``nth`` ``Weekday name`` of the month of ``Month name`` + +Or even better for Election day, which is: + +> the Tuesday next after the first Monday in the month of November. + +We've got you covered with static methods in the core ``Calendar`` class. + +```python +>>> from calendra.core import Calendar, THU +>>> Calendar.get_nth_weekday_in_month(2018, 11, THU) # by default, find the first +datetime.date(2018, 11, 2) +>>> Calendar.get_nth_weekday_in_month(2018, 11, THU, 4) # Thanksgiving +datetime.date(2018, 11, 23) +``` + +If you want to find the 2nd Monday after the 4th of July, you can use the ``start`` argument: + +```python +>>> from datetime import date +>>> from calendra.core import Calendar, MON +>>> Calendar.get_nth_weekday_in_month( + 2018, # Year + 7, # Month + MON, # Monday + 2, # The 2nd one + start=date(2018, 7, 9)) +datetime.date(2018, 7, 16) +``` + +## Find the last Monday in the Month + +This one was a bit trickier, because it may happen that you have 4 or 5 `weekday name` in the same month. e.g: during the month of July 2018, you have 5 Mondays (2nd, 9th, 16th, 23rd, 30th), but only 4 Saturdays (7th, 14th, 21st, 28th). + +Same as above, there's a static method in the ``Calendar`` class: + +```python +>>> from calendra.core import Calendar, MON, FRI +>>> Calendar.get_last_weekday_in_month(2018, 7, MON) +datetime.date(2018, 7, 30) +>>> Calendar.get_last_weekday_in_month(2018, 7, FRI) +datetime.date(2018, 7, 27) +``` + +## Find the first Monday after a given date + +Colombia, as an example, states that the Epiphany Day is set to the "First Monday after the 6th of January". You may use the following function to find this specific formula: + +```python +>>> from calendra.core import Calendar, MON +>>> from datetime import date +>>> jan_6th = date(2018, 1, 6) # It's a Saturday +>>> Calendar.get_first_weekday_after(jan_6th, MON) +datetime.date(2018, 1, 8) +``` + +[Home](index.md) / [Basic usage](basic.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) diff --git a/docs/basic.md b/docs/basic.md new file mode 100644 index 00000000..262ce169 --- /dev/null +++ b/docs/basic.md @@ -0,0 +1,112 @@ +# Basic usage + +[Home](index.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) + +Here are basic examples of what Calendra can do for you. As an integrator or a simple Calendra user, you will use these methods to retrieve calendars, and get basic outputs for a given date. + +## Get holidays for a given country and year + +```python +>>> from calendra.europe import France +>>> cal = France() +>>> cal.holidays(2012) +[(datetime.date(2012, 1, 1), 'New year'), + (datetime.date(2012, 4, 9), 'Easter Monday'), + (datetime.date(2012, 5, 1), 'Labour Day'), + (datetime.date(2012, 5, 8), 'Victory in Europe Day'), + (datetime.date(2012, 5, 17), 'Ascension Day'), + (datetime.date(2012, 5, 28), 'Whit Monday'), + (datetime.date(2012, 7, 14), 'Bastille Day'), + (datetime.date(2012, 8, 15), 'Assumption of Mary to Heaven'), + (datetime.date(2012, 11, 1), "All Saints' Day"), + (datetime.date(2012, 11, 11), 'Armistice Day'), + (datetime.date(2012, 12, 25), 'Christmas')] +``` + +As you can see, the output is a simple list of tuples, with the first member being a `datetime.date` object, and the second one is the holiday label as a string. By design, we have chosen to stick to basic types so you can use them in your application and eventually compose your personal classes using them. + +## Know if a day is a holiday or not + +```python +>>> from datetime import date +>>> from calendra.europe import France +>>> cal = France() +>>> cal.is_working_day(date(2012, 12, 25)) # it's Christmas +False +>>> cal.is_working_day(date(2012, 12, 30)) # it's a Sunday +False +>>> cal.is_working_day(date(2012, 12, 26)) # No Boxing day in France +True +``` + +## Compute the "nth" working day after a given date + +That was the basic use-case that started this library development: to answer to the following question: + +> This task is due in 5 working days, starting of today. Can we calculate that? + +In the following example, we want to calculate 5 working days after December the 23rd 2012. + +| Date | Weekday | Working Day? | Count | +|:-----|:-------:|:------------:|:-----:| +| 23rd | SUN | No | 0 | +| 24th | MON | Yes | +1 | +| 25th | TUE | No (XMas) | - | +| 26th | WED | Yes | +2 | +| 27th | THU | Yes | +3 | +| 28th | FRI | Yes | +4 | +| 29th | SAT | No (weekend) | - | +| 30th | SUN | No (weekend) | - | +| 31th | MON | Yes | +5 | + +```python +>>> from datetime import date +>>> from calendra.europe import France +>>> cal = France() +>>> cal.add_working_days(date(2012, 12, 23), 5) +datetime.date(2012, 12, 31) +``` + +If we had requested the 6th day after the 23rd, it would have been January 2nd, 2013, because January 1st is New Year's Day. + +## Calculate the number or working days between two given days + +Let's say you want to know how many working days there are between April 2nd and June 17th of 2018, in France. Use the following: + +```python +>>> from datetime import date +>>> from calendra.europe import France +>>> cal = France() +>>> cal.get_working_days_delta(date(2018, 4, 2), date(2018, 6, 17)) +50 +``` + +## Standard date(time) types only, please! + +For your convenience, we allow both `datetime.date` and `datetime.datetime` types (and their subclasses) when using the core functions. + +**WARNING**: We'll only allow "dates" types coming from the Python standard library. If you're manipulating types from external library. Trying to pass a non-standard argument will result in raising a ``UnsupportedDateType`` error. + +Example: + +```python +>>> from datetime import date, datetime +>>> from calendra.europe import France +>>> cal = France() +>>> cal.is_working_day(datetime(2012, 12, 25, 14, 0, 39)) +False +>>> cal.add_working_days(datetime(2012, 12, 23, 14, 0, 39), 5) +datetime.datetime(2012, 12, 31) +``` + +If you really need it, you can use the ``add_working_days()`` with an option that will keep the ``datetime`` type: + +```python +>>> from datetime import date, datetime +>>> from calendra.europe import France +>>> cal = France() +>>> cal.add_working_days(datetime(2012, 12, 23, 14, 0, 39), 5, keep_datetime=True) +datetime.datetime(2012, 12, 31, 14, 0, 39) +``` + +[Home](index.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) diff --git a/docs/class-options.md b/docs/class-options.md new file mode 100644 index 00000000..c2dddb20 --- /dev/null +++ b/docs/class-options.md @@ -0,0 +1,108 @@ +# Advanced feature: class options + +[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) + + +As of `v13.0.0` you can define *options* for your calendar. + +## Options as "flags" + +Let's consider the following calendar class: + +```python +@iso_register('ZK') +class Zhraa(WesternCalendar): + include_easter_monday = True + include_labour_day = True + FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( + (8, 2, "King Birthday"), + ) + + def get_variable_days(self, year): + # usual variable days + days = super().get_variable_days(year) + + days.append( + (Zhraa.get_nth_weekday_in_month(year, 6, MON), + 'Day of the Founder'), + ) + return days +``` + +In our example, we'll add a special holiday. It's **not** an official holiday, but it **can** be taken as a non-working day, if your organization or your administration decides it becomes one. + +For the sake of our use-case, let's say that the Zhraa Kingdom decides that January the 2nd can be considered as a day off in some special cases, for banks or schools, for example. Or at the discretion of your company. + +As an integrator, if you need to implement this behaviour, you can decide to have a separate class which includes your extra day. This would fit your needs. + +But what if your special organization decides to keep this day as a non-working day depending on a variable context? Let's say that it does not happen every year, or it's not in every city of the Kingdom, or not for all of your employees, etc. Using a dedicated class won't help you there. +But **optional arguments** surely will! + +Here's a slightly different version of our `Zhraa` class: + + +```python +@iso_register('ZK') +class Zhraa(WesternCalendar): + include_easter_monday = True + include_labour_day = True + FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( + (8, 2, "King Birthday"), + ) + + def __init__(self, include_january_2nd=False, **kwargs): + super().__init__(**kwargs) + self.include_january_2nd = include_january_2nd + + def get_variable_days(self, year): + # usual variable days + days = super().get_variable_days(year) + + days.append( + (Zhraa.get_nth_weekday_in_month(year, 6, MON), + 'Day of the Founder'), + ) + + if self.include_january_2nd: + days.append( + (date(year, 1, 2), "January 2nd") + ) + return days +``` + +In your *business-logic code*, you could then write: + +```python +include_january_2nd = year == 2019 \ + and (employee_name == "Bob" or employee_location == "North") +calendar = Zhraa(include_january_2nd=include_january_2nd) +``` + +As a consequence, depending on the context required by your business-logic process, you can include or exclude January 2nd as a holiday. + +## More than "flags" + +Of course, **you are not limited to booleans** to activate/deactivate a holiday when you want to create options. Use strings, numbers, objects, or whatever suits your needs. + +Here are some examples: + +* A ``region`` argument: in some regions, it defines new holidays, exceptions on including or excluding other days, etc. +* A `number_of_days_after_new_year` as a variable number of days that are to be accounted as non-working days after New Year's day. +* A ``day_of_the_founder_label`` string variable to easily customize the *Day of the Founder* label, according to your liking. +* ... sky is the limit! + +## Caveats + +### Why not using derivative classes? + +Again, for each of these options, you could define an inherited class of `Zhraa`. But as you have more and more option values and exceptions to your rules, you'd have to define a new class for each of these combinations. + +If you only have a couple of exceptions to your rules, you may prefer ot have a dedicated class. If the number of combinations goes over a limit, opt for *options*. + +### ERR_TOO_MANY_OPTIONS + +**Beware!** Options are nice and will help you in many cases, but it's probably a bad idea to go on and add dozens of them to your class constructor. As a consequence, your runtime code would be more and more complex. You'd have hard time covering all of the combinations around their values and test them. + +As in many other cases, your mileage may vary, but I doubt that you want to combine more than 5 of them. + +[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [ISO Registry](iso-registry.md) / [iCal Export](ical.md) / [Contributing](contributing.md) diff --git a/docs/ical.md b/docs/ical.md new file mode 100644 index 00000000..0037a90a --- /dev/null +++ b/docs/ical.md @@ -0,0 +1,71 @@ +# iCal export + +[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [Contributing](contributing.md) + +As of v11.0.0, you can export holidays generated by any Workalendar class to the iCal file format. + +## Export and retrieve the content + +In this example, we export all the public holidays for France from the year 2019 to the year 2022 (inclusive). + +```python +>>> from workalendar.europe import France +>>> cal = France() +>>> ical_content = cal.export_to_ical(period=[2019, 2022]) +>>> print(ical_content) +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//workalendar//ical 9.0.0//EN +BEGIN:VEVENT +SUMMARY:New year +DTSTART;VALUE=DATE:20190101 +DTSTAMP;VALUE=DATE-TIME:20200828T152009Z +UID:2019-01-01New year@peopledoc.github.io/workalendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:Easter Monday +DTSTART;VALUE=DATE:20190422 +DTSTAMP;VALUE=DATE-TIME:20200828T152009Z +UID:2019-04-22Easter Monday@peopledoc.github.io/workalendar +END:VEVENT +BEGIN:VEVENT +SUMMARY:Labour Day +DTSTART;VALUE=DATE:20190501 +DTSTAMP;VALUE=DATE-TIME:20200828T152009Z +UID:2019-05-01Labour Day@peopledoc.github.io/workalendar +END:VEVENT +# ... +``` + +You may store this content into a file, or do anything else you want with it. + +If you don't provide any period, it defaults to [2000, 2030]. + +## Export and save it to a file + +You can also directly save your ical export to a file. The following example writes a ``france.ics`` file to your disk, with the holidays between 2019 and 2022. + +```python +>>> from workalendar.europe import France +>>> cal = France() +>>> cal.export_to_ical(period=[2019, 2022], target_path="france.ics") +``` + +**Note:** Please note that the export function won't work if the path is unwriteable or if it points at an existing directory. + +### Known file extensions + +The `target_path` argument may be provided with or without a file extension. For example, it can be either: + +| `target_path` argument | filename generated | +|:-----------------------|:-------------------| +| `france` | `france.ics` | +| `france.ics` | `france.ics` | +| `france.ical` | `france.ical` | +| `france.ifb` | `france.ifb` | +| `france.icalendar` | `france.icalendar` | +| `france.txt` | `france.txt.ics` | + +As you see, we only add the `.ics` extension if the current `target_path` extension is not known. + +[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [ISO Registry](iso-registry.md) / [Contributing](contributing.md) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..50917e45 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,23 @@ +# Calendra documentation + +## Overview + +Calendra is a Python module that offers classes able to handle calendars, list legal / religious holidays and gives working-day-related computation functions. + +## Status + +This library is ready for production, although we may warn eventual users: some calendars may not be up-to-date, and this library doesn't cover all the existing countries on earth (yet). + +## Available Calendars + +See [the repository README](https://github.com/jaraco/calendra#available-calendars) for the most up-to-date catalog of the available calendars. + +## Usage examples + +* [Basic usage](basic.md) +* [Advanced usage](advanced.md) +* Advanced features: + * [Class options](class-options.md) + * [ISO Registry](iso-registry.md) + * [iCal Export](ical.md) +* [How to contribute](contributing.md) diff --git a/docs/iso-registry.md b/docs/iso-registry.md new file mode 100644 index 00000000..64b30300 --- /dev/null +++ b/docs/iso-registry.md @@ -0,0 +1,153 @@ +# The ISO registry + +[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [iCal Export](ical.md) / [Contributing](contributing.md) + +As of version 3.0 (August/September 2018), we have introduced a global calendar registry for calendars related to a country or a region that belongs to the [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) or the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) for sub-regions (such as USA states, Australian territories or Canadian provinces and such). + +## Iterate over the whole registry + +```python +>>> from workalendar.registry import registry +>>> calendars = registry.get_calendars() # This returns a dictionary +>>> for code, calendar_class in calendars.items(): +... print("`{}` is code for '{}'".format(code, calendar_class.name)) +`AT` is code for 'Austria' +`BE` is code for 'Belgium' +`BG` is code for 'Bulgaria' +`HR` is code for 'Croatia' +`CY` is code for 'Cyprus' +`CZ` is code for 'Czech Republic' +`EE` is code for 'Estonia' +`DK` is code for 'Denmark' +`FI` is code for 'Finland' +`FR` is code for 'France' +... continued +``` + +The "private property" `registry.region_registry` is a `dict` object, with the ISO code as a key, and the calendar class as the value. As a "workalendar standard", **every** calendar in the registry has a `name` property (derived from the docstring), so you'd probably be able to build a user-friendly list of available calendars, for a dropdown list, for example. + +**DEPRECATION WARNING**: As of version 9.0.0, the ``IsoRegistry.items()`` has been renamed into ``IsoRegistry.get_calendars()`` for all your queries in the registry. + +## Retrieve a collection of regions + +If you want the full dictionary of **countries**, you can use the ``get_calendars()`` method. + +```python +>>> registry.get_calendars() +{'AT': , + 'BE': , + 'BG': , + 'KY': , + # ... continued +} +``` + +Let's say you'd need only a subset of the ISO registry. For example, France, Switzerland and Canada calendars. You can use the method `get_calendars()` to filter only the calendars you want. + +```python +>>> registry.get_calendars(['FR', 'CH', 'CA']) +{'FR': , + 'CH': , + 'CA': } +``` + +Also, if you want those regions **and** their subregions, you can use the `include_subregions` flag: + +```python +>>> registry.get_calendars(['FR', 'CH', 'CA'], include_subregions=True) +{'FR': workalendar.europe.france.France, + 'CH': workalendar.europe.switzerland.Switzerland, + 'CH-AG': workalendar.europe.switzerland.Aargau, + 'CH-AI': workalendar.europe.switzerland.AppenzellInnerrhoden, + 'CH-AR': workalendar.europe.switzerland.AppenzellAusserrhoden, + 'CH-BE': workalendar.europe.switzerland.Bern, + 'CH-BL': workalendar.europe.switzerland.BaselLandschaft, + 'CH-BS': workalendar.europe.switzerland.BaselStadt, + 'CH-FR': workalendar.europe.switzerland.Fribourg, + 'CH-GE': workalendar.europe.switzerland.Geneva, + 'CH-GL': workalendar.europe.switzerland.Glarus, + 'CH-GR': workalendar.europe.switzerland.Graubunden, + 'CH-JU': workalendar.europe.switzerland.Jura, + 'CH-LU': workalendar.europe.switzerland.Luzern, + 'CH-NE': workalendar.europe.switzerland.Neuchatel, + 'CH-NW': workalendar.europe.switzerland.Nidwalden, + 'CH-OW': workalendar.europe.switzerland.Obwalden, + 'CH-SG': workalendar.europe.switzerland.StGallen, + 'CH-SH': workalendar.europe.switzerland.Schaffhausen, + 'CH-SO': workalendar.europe.switzerland.Solothurn, + 'CH-SZ': workalendar.europe.switzerland.Schwyz, + 'CH-TG': workalendar.europe.switzerland.Thurgau, + 'CH-TI': workalendar.europe.switzerland.Ticino, + 'CH-UR': workalendar.europe.switzerland.Uri, + 'CH-VD': workalendar.europe.switzerland.Vaud, + 'CH-VS': workalendar.europe.switzerland.Valais, + 'CH-ZG': workalendar.europe.switzerland.Zug, + 'CH-ZH': workalendar.europe.switzerland.Zurich, + 'CA': workalendar.america.canada.Canada, + 'CA-ON': workalendar.america.canada.Ontario, + 'CA-QC': workalendar.america.canada.Quebec, + 'CA-BC': workalendar.america.canada.BritishColumbia, + 'CA-AB': workalendar.america.canada.Alberta, + 'CA-SK': workalendar.america.canada.Saskatchewan, + 'CA-MB': workalendar.america.canada.Manitoba, + 'CA-NB': workalendar.america.canada.NewBrunswick, + 'CA-NS': workalendar.america.canada.NovaScotia, + 'CA-PE': workalendar.america.canada.PrinceEdwardIsland, + 'CA-NL': workalendar.america.canada.Newfoundland, + 'CA-YT': workalendar.america.canada.Yukon, + 'CA-NT': workalendar.america.canada.NorthwestTerritories, + 'CA-NU': workalendar.america.canada.Nunavut} +``` + +*Note*: if any of the codes is unknown, this function won't raise an error. + +You can also get the full dict of all calendars registered in the ISO Registry with all the subregions using the following function call: + +```python +>>> registry.get_calendars(include_subregions=True) +``` + +## Select only one calendar + +Let's say that we only know the ISO code for Switzerland (`CH`). If we want to compute holidays for Switzerland in 2018, we can do as follows: + +```python +>>> CalendarClass = registry.get('CH') +>>> calendar = CalendarClass() +>>> calendar.holidays(2018) +[(datetime.date(2018, 1, 1), 'New year'), + (datetime.date(2018, 3, 30), 'Good Friday'), + (datetime.date(2018, 4, 1), 'Easter Sunday'), + (datetime.date(2018, 4, 2), 'Easter Monday'), + (datetime.date(2018, 5, 10), 'Ascension Thursday'), + (datetime.date(2018, 5, 20), 'Whit Sunday'), + (datetime.date(2018, 5, 21), 'Whit Monday'), + (datetime.date(2018, 8, 1), 'National Holiday'), + (datetime.date(2018, 12, 25), 'Christmas Day'), + (datetime.date(2018, 12, 26), 'Boxing Day')] +``` + +*Note*: this function would return `None` if the code is unknown. + +**DEPRECATION WARNING**: As of version 10.0.0, the ``IsoRegistry.get_calendar_class()`` has been renamed into ``IsoRegistry.get()`` to retrieve a single calendar class out of the registry. + + +## Select only sub-regions + +As an example, the United States of America, or Australia and others are divided into "sub-regions" (states, provinces, territories, etc). There are usually Federal or State holidays, and variants depending on the region you're targeting. + +```python +>>> registry.get_subregions('AU') +{'AU-ACT': , + 'AU-NSW': , + 'AU-NT': , + 'AU-QLD': , + 'AU-SA': , + 'AU-TAS': , + 'AU-VIC': , + 'AU-WA': } +``` + +*Note*: this function will return an empty `dict` if the code is unknown. + +[Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) / [Class options](class-options.md) / [iCal Export](ical.md) / [Contributing](contributing.md) From 3e876d7906fa6387ab6ac9a9bff8659762363017 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Jan 2021 23:14:07 -0500 Subject: [PATCH 378/447] Enable complexity limit. Fixes jaraco/skeleton#34. --- .flake8 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.flake8 b/.flake8 index 790c109f..59a51f86 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,9 @@ [flake8] max-line-length = 88 + +# jaraco/skeleton#34 +max-complexity = 10 + ignore = # W503 violates spec https://github.com/PyCQA/pycodestyle/issues/513 W503 From 1731fbebe9f6655a203e6e08ab309f9916ea6f65 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sat, 9 Jan 2021 05:21:12 +0100 Subject: [PATCH 379/447] Replace pep517.build with build (#37) * Replace pep517.build with build Resolves #30 * Prefer simple usage Co-authored-by: Jason R. Coombs --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 7233b942..249f97c2 100644 --- a/tox.ini +++ b/tox.ini @@ -24,7 +24,7 @@ commands = [testenv:release] skip_install = True deps = - pep517>=0.5 + build twine[keyring]>=1.13 path jaraco.develop>=7.1 @@ -35,6 +35,6 @@ setenv = TWINE_USERNAME = {env:TWINE_USERNAME:__token__} commands = python -c "import path; path.Path('dist').rmtree_p()" - python -m pep517.build . + python -m build python -m twine upload dist/* python -m jaraco.develop.create-github-release From a9b3f681dea9728235c2a9c68165f7b5cbf350ab Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sat, 9 Jan 2021 05:27:24 +0100 Subject: [PATCH 380/447] Use license_files instead of license_file in meta (#35) Singular `license_file` is deprecated since wheel v0.32.0. Refs: * https://wheel.readthedocs.io/en/stable/news.html * https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index d5010f70..88bc263a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,6 @@ [metadata] -license_file = LICENSE +license_files = + LICENSE name = skeleton author = Jason R. Coombs author_email = jaraco@jaraco.com From 77fbe1df4af6d8f75f44440e89ee1bc249c9f2e0 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sat, 9 Jan 2021 05:37:11 +0100 Subject: [PATCH 381/447] Use `extend-ignore` in flake8 config (#33) * Use `extend-ignore` in flake8 config This option allows to add extra ignored rules to the default list instead of replacing it. The default exclusions are: E121, E123, E126, E226, E24, E704, W503 and W504. Fixes #28. Refs: * https://github.com/pypa/setuptools/pull/2486/files#r541943356 * https://flake8.pycqa.org/en/latest/user/options.html#cmdoption-flake8-extend-ignore * https://flake8.pycqa.org/en/latest/user/options.html#cmdoption-flake8-ignore * Enable complexity limit. Fixes jaraco/skeleton#34. * Replace pep517.build with build (#37) * Replace pep517.build with build Resolves #30 * Prefer simple usage Co-authored-by: Jason R. Coombs * Use license_files instead of license_file in meta (#35) Singular `license_file` is deprecated since wheel v0.32.0. Refs: * https://wheel.readthedocs.io/en/stable/news.html * https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file Co-authored-by: Jason R. Coombs --- .flake8 | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.flake8 b/.flake8 index 59a51f86..48b2e246 100644 --- a/.flake8 +++ b/.flake8 @@ -4,10 +4,6 @@ max-line-length = 88 # jaraco/skeleton#34 max-complexity = 10 -ignore = - # W503 violates spec https://github.com/PyCQA/pycodestyle/issues/513 - W503 - # W504 has issues https://github.com/OCA/maintainer-quality-tools/issues/545 - W504 +extend-ignore = # Black creates whitespace before colon E203 From 0df40810ec54590c888ae0e4073d73f731c91f4a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 15 Jan 2021 19:16:28 -0500 Subject: [PATCH 382/447] Add support for namespace packages. Closes jaraco/skeleton#40. --- setup.cfg | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 88bc263a..106763e3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,12 +15,18 @@ classifiers = Programming Language :: Python :: 3 :: Only [options] -packages = find: +packages = find_namespace: include_package_data = true python_requires = >=3.6 install_requires = setup_requires = setuptools_scm[toml] >= 3.4.1 +[options.packages.find] +exclude = + build* + docs* + tests* + [options.extras_require] testing = # upstream From 2afe018a289a1f4f0acbd7d2b8b7f3810f367af3 Mon Sep 17 00:00:00 2001 From: Shaheed Haque Date: Mon, 18 Jan 2021 12:58:15 +0000 Subject: [PATCH 383/447] Document the "series" holiday feature (also correct some indentation). --- CHANGES.rst | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 439a6045..b25c0265 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,14 @@ v7.0.0 ------- +New feature + +- Enhanced support for multi-day "series" holidays such as Chinese Spring + Festival and Islamic Eid. Previously, if one day of the series was shifted + as per the observance shift rules, it would "merge" into the next day of the + series, effectively shortening the observed series. Now, all the following + days of the series are shifted, maintaining its duration. + Incorporate changes from workalendar v14.1.0 (2020-12-10) - Fix Russia 2021 holidays, thx @MichalChromcak for the bug report (#578). @@ -75,14 +83,14 @@ Incorporate changes from workalendar v11.0.1 (2020-09-11) Incorporate changes from workalendar v11.0.0 (2020-09-04) -New calendar +- New calendar -- Added Mozambique calendar by @mr-shovel (#542). + - Added Mozambique calendar by @mr-shovel (#542). -New feature +- New feature -- Added iCal export feature, initiated by @joooeey (#197). -- Fix PRODID pattern for iCal exports: `"PRODID:-//workalendar//ical {__version__}//EN"`, using current workalendar version (#543). + - Added iCal export feature, initiated by @joooeey (#197). + - Fix PRODID pattern for iCal exports: `"PRODID:-//workalendar//ical {__version__}//EN"`, using current workalendar version (#543). Incorporate changes from workalendar v10.4.0 (2020-08-28) From 51298a2cc4faa7253e9fe41d7a9574cf9aac997c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 9 Feb 2021 23:08:58 -0500 Subject: [PATCH 384/447] Normalize indentation --- setup.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 106763e3..8df8d273 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,9 +23,9 @@ setup_requires = setuptools_scm[toml] >= 3.4.1 [options.packages.find] exclude = - build* - docs* - tests* + build* + docs* + tests* [options.extras_require] testing = From 743af7249d56e55a7c2c5f3111958ceee008d8ea Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 21 Feb 2021 13:04:46 -0500 Subject: [PATCH 385/447] Exclude dist from discovered packages. Fixes jaraco/skeleton#46. --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 8df8d273..af246415 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,7 @@ setup_requires = setuptools_scm[toml] >= 3.4.1 [options.packages.find] exclude = build* + dist* docs* tests* From 38fff62edb5e282f144dc77cc1bf5555367336d9 Mon Sep 17 00:00:00 2001 From: KOLANICH Date: Sat, 6 Feb 2021 23:03:13 +0300 Subject: [PATCH 386/447] Added an .editorconfig. Pull request jaraco/skeleton#43. --- .editorconfig | 15 +++++++++++++++ pytest.ini | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..6385b573 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +indent_style = tab +indent_size = 4 +insert_final_newline = true +end_of_line = lf + +[*.py] +indent_style = space + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 diff --git a/pytest.ini b/pytest.ini index d7f0b115..016063b5 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,5 +5,5 @@ doctest_optionflags=ALLOW_UNICODE ELLIPSIS # workaround for warning pytest-dev/pytest#6178 junit_family=xunit2 filterwarnings= - # https://github.com/pytest-dev/pytest/issues/6928 - ignore:direct construction of .*Item has been deprecated:DeprecationWarning + # https://github.com/pytest-dev/pytest/issues/6928 + ignore:direct construction of .*Item has been deprecated:DeprecationWarning From 5e416793c008c5ef285c37828072fbea5ced6d08 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 21 Feb 2021 21:34:35 -0500 Subject: [PATCH 387/447] It's no longer necessary to filter this warning and it's not a warning anymore. --- pytest.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/pytest.ini b/pytest.ini index 016063b5..6bf69af1 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,5 +5,3 @@ doctest_optionflags=ALLOW_UNICODE ELLIPSIS # workaround for warning pytest-dev/pytest#6178 junit_family=xunit2 filterwarnings= - # https://github.com/pytest-dev/pytest/issues/6928 - ignore:direct construction of .*Item has been deprecated:DeprecationWarning From d9a13c77ce2a3efea70c97d219ca4335c0f03c40 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 21 Feb 2021 21:36:53 -0500 Subject: [PATCH 388/447] Bump minimum pytest --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index af246415..81f70eea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,7 +31,7 @@ exclude = [options.extras_require] testing = # upstream - pytest >= 3.5, !=3.7.3 + pytest >= 4.6 pytest-checkdocs >= 1.2.3 pytest-flake8 pytest-black >= 0.3.7; python_implementation != "PyPy" From bf9fae2c0df316dc837d56ae68880620733d5ff6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 6 Mar 2021 09:57:43 -0500 Subject: [PATCH 389/447] Require twine 3 with keyring unconditionally required. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 249f97c2..a9a50b01 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,7 @@ commands = skip_install = True deps = build - twine[keyring]>=1.13 + twine>=3 path jaraco.develop>=7.1 passenv = From 7bdab57872da46ef6a5a7f5ea9099a197bdc3131 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 7 Mar 2021 12:23:48 -0500 Subject: [PATCH 390/447] Add comments indicating why the exclusions are present --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index 81f70eea..dd215c65 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,8 +34,10 @@ testing = pytest >= 4.6 pytest-checkdocs >= 1.2.3 pytest-flake8 + # python_implementation: workaround for jaraco/skeleton#22 pytest-black >= 0.3.7; python_implementation != "PyPy" pytest-cov + # python_implementation: workaround for jaraco/skeleton#22 pytest-mypy; python_implementation != "PyPy" pytest-enabler From 14312a5bd75d3313ffd3e14fc7fbbc2a9b05cee5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 7 Mar 2021 12:24:21 -0500 Subject: [PATCH 391/447] Exclude mypy on Python 3.10 as workaround for python/typed_ast#156. --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index dd215c65..55497f8e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,7 +38,8 @@ testing = pytest-black >= 0.3.7; python_implementation != "PyPy" pytest-cov # python_implementation: workaround for jaraco/skeleton#22 - pytest-mypy; python_implementation != "PyPy" + # python_version: workaround for python/typed_ast#156 + pytest-mypy; python_implementation != "PyPy" and python_version < "3.10" pytest-enabler # local From af5445115af0cb68e671a678538a0207389586be Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 7 Mar 2021 12:30:25 -0500 Subject: [PATCH 392/447] Bump minimums on pytest-checkdocs and pytest-enabler as found on Setuptools. --- setup.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 55497f8e..3f6610be 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,7 +32,7 @@ exclude = testing = # upstream pytest >= 4.6 - pytest-checkdocs >= 1.2.3 + pytest-checkdocs >= 2.4 pytest-flake8 # python_implementation: workaround for jaraco/skeleton#22 pytest-black >= 0.3.7; python_implementation != "PyPy" @@ -40,7 +40,7 @@ testing = # python_implementation: workaround for jaraco/skeleton#22 # python_version: workaround for python/typed_ast#156 pytest-mypy; python_implementation != "PyPy" and python_version < "3.10" - pytest-enabler + pytest-enabler >= 1.0.1 # local From 86efb884f805a9e1f64661ec758f3bd084fed515 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 7 Mar 2021 12:53:54 -0500 Subject: [PATCH 393/447] Also deny black on Python 3.10 as workaround for python/typed_ast#156. --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 3f6610be..52876d55 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,7 +35,8 @@ testing = pytest-checkdocs >= 2.4 pytest-flake8 # python_implementation: workaround for jaraco/skeleton#22 - pytest-black >= 0.3.7; python_implementation != "PyPy" + # python_version: workaround for python/typed_ast#156 + pytest-black >= 0.3.7; python_implementation != "PyPy" and python_version < "3.10" pytest-cov # python_implementation: workaround for jaraco/skeleton#22 # python_version: workaround for python/typed_ast#156 From 7fe4ab8294a843622d20face7f9f6ccddb2d0a14 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 15 Mar 2021 18:31:04 -0400 Subject: [PATCH 394/447] Add leading */ to coverage.run.omit. Workaround for pytest-dev/pytest-cov#456. --- .coveragerc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 45823064..6a34e662 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,5 +1,7 @@ [run] -omit = .tox/* +omit = + # leading `*/` for pytest-dev/pytest-cov#456 + */.tox/* [report] show_missing = True From 6e2d0ba00b60c10466b0e040e2d4b1206c3f0b3d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 25 Apr 2021 20:38:58 -0400 Subject: [PATCH 395/447] Remove automerge. Fixes jaraco/skeleton#49. --- .github/workflows/automerge.yml | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 .github/workflows/automerge.yml diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml deleted file mode 100644 index 4f70acfb..00000000 --- a/.github/workflows/automerge.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: automerge -on: - pull_request: - types: - - labeled - - unlabeled - - synchronize - - opened - - edited - - ready_for_review - - reopened - - unlocked - pull_request_review: - types: - - submitted - check_suite: - types: - - completed - status: {} -jobs: - automerge: - runs-on: ubuntu-latest - steps: - - name: automerge - uses: "pascalgn/automerge-action@v0.12.0" - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" From 2f690f6083feea9a16ea3711f391d598a2ed1228 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 25 Apr 2021 21:19:48 -0400 Subject: [PATCH 396/447] Enable dependabot (#50) * Added a config for dependabot. * Update features list for dependabot. Co-authored-by: KOLANICH --- .github/dependabot.yml | 8 ++++++++ skeleton.md | 1 + 2 files changed, 9 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..89ff3396 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + allow: + - dependency-type: "all" diff --git a/skeleton.md b/skeleton.md index 0938f892..af5f2ca2 100644 --- a/skeleton.md +++ b/skeleton.md @@ -77,6 +77,7 @@ The features/techniques employed by the skeleton include: - A CHANGES.rst file intended for publishing release notes about the project - Use of [Black](https://black.readthedocs.io/en/stable/) for code formatting (disabled on unsupported Python 3.5 and earlier) - Integrated type checking through [mypy](https://github.com/python/mypy/). +- Dependabot enabled to enable supply chain security. ## Packaging Conventions From 6c1c45bc1ce8ab01d91324a46c584172664a0104 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 25 Apr 2021 21:52:29 -0400 Subject: [PATCH 397/447] Replace md file with badge linking to documentation site. Fixes jaraco/skeleton#47. --- README.rst | 3 + skeleton.md | 167 ---------------------------------------------------- 2 files changed, 3 insertions(+), 167 deletions(-) delete mode 100644 skeleton.md diff --git a/README.rst b/README.rst index 128e61e4..a3e1b740 100644 --- a/README.rst +++ b/README.rst @@ -16,3 +16,6 @@ .. .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest .. :target: https://skeleton.readthedocs.io/en/latest/?badge=latest + +.. image:: https://img.shields.io/badge/skeleton-2021-informational + :target: https://blog.jaraco.com/skeleton diff --git a/skeleton.md b/skeleton.md deleted file mode 100644 index af5f2ca2..00000000 --- a/skeleton.md +++ /dev/null @@ -1,167 +0,0 @@ -# Overview - -This project is merged with [skeleton](https://github.com/jaraco/skeleton). What is skeleton? It's the scaffolding of a Python project jaraco [introduced in his blog](https://blog.jaraco.com/a-project-skeleton-for-python-projects/). It seeks to provide a means to re-use techniques and inherit advances when managing projects for distribution. - -## An SCM-Managed Approach - -While maintaining dozens of projects in PyPI, jaraco derives best practices for project distribution and publishes them in the [skeleton repo](https://github.com/jaraco/skeleton), a Git repo capturing the evolution and culmination of these best practices. - -It's intended to be used by a new or existing project to adopt these practices and honed and proven techniques. Adopters are encouraged to use the project directly and maintain a small deviation from the technique, make their own fork for more substantial changes unique to their environment or preferences, or simply adopt the skeleton once and abandon it thereafter. - -The primary advantage to using an SCM for maintaining these techniques is that those tools help facilitate the merge between the template and its adopting projects. - -Another advantage to using an SCM-managed approach is that tools like GitHub recognize that a change in the skeleton is the _same change_ across all projects that merge with that skeleton. Without the ancestry, with a traditional copy/paste approach, a [commit like this](https://github.com/jaraco/skeleton/commit/12eed1326e1bc26ce256e7b3f8cd8d3a5beab2d5) would produce notifications in the upstream project issue for each and every application, but because it's centralized, GitHub provides just the one notification when the change is added to the skeleton. - -# Usage - -## new projects - -To use skeleton for a new project, simply pull the skeleton into a new project: - -``` -$ git init my-new-project -$ cd my-new-project -$ git pull gh://jaraco/skeleton -``` - -Now customize the project to suit your individual project needs. - -## existing projects - -If you have an existing project, you can still incorporate the skeleton by merging it into the codebase. - -``` -$ git merge skeleton --allow-unrelated-histories -``` - -The `--allow-unrelated-histories` is necessary because the history from the skeleton was previously unrelated to the existing codebase. Resolve any merge conflicts and commit to the master, and now the project is based on the shared skeleton. - -## Updating - -Whenever a change is needed or desired for the general technique for packaging, it can be made in the skeleton project and then merged into each of the derived projects as needed, recommended before each release. As a result, features and best practices for packaging are centrally maintained and readily trickle into a whole suite of packages. This technique lowers the amount of tedious work necessary to create or maintain a project, and coupled with other techniques like continuous integration and deployment, lowers the cost of creating and maintaining refined Python projects to just a few, familiar Git operations. - -For example, here's a session of the [path project](https://pypi.org/project/path) pulling non-conflicting changes from the skeleton: - - - -Thereafter, the target project can make whatever customizations it deems relevant to the scaffolding. The project may even at some point decide that the divergence is too great to merit renewed merging with the original skeleton. This approach applies maximal guidance while creating minimal constraints. - -## Periodic Collapse - -In late 2020, this project [introduced](https://github.com/jaraco/skeleton/issues/27) the idea of a periodic but infrequent (O(years)) collapse of commits to limit the number of commits a new consumer will need to accept to adopt the skeleton. - -The full history of commits is collapsed into a single commit and that commit becomes the new mainline head. - -When one of these collapse operations happens, any project that previously pulled from the skeleton will no longer have a related history with that new main branch. For those projects, the skeleton provides a "handoff" branch that reconciles the two branches. Any project that has previously merged with the skeleton but now gets an error "fatal: refusing to merge unrelated histories" should instead use the handoff branch once to incorporate the new main branch. - -``` -$ git pull https://github.com/jaraco/skeleton 2020-handoff -``` - -This handoff needs to be pulled just once and thereafter the project can pull from the main head. - -The archive and handoff branches from prior collapses are indicate here: - -| refresh | archive | handoff | -|---------|-----------------|--------------| -| 2020-12 | archive/2020-12 | 2020-handoff | - -# Features - -The features/techniques employed by the skeleton include: - -- PEP 517/518-based build relying on Setuptools as the build tool -- Setuptools declarative configuration using setup.cfg -- tox for running tests -- A README.rst as reStructuredText with some popular badges, but with Read the Docs and AppVeyor badges commented out -- A CHANGES.rst file intended for publishing release notes about the project -- Use of [Black](https://black.readthedocs.io/en/stable/) for code formatting (disabled on unsupported Python 3.5 and earlier) -- Integrated type checking through [mypy](https://github.com/python/mypy/). -- Dependabot enabled to enable supply chain security. - -## Packaging Conventions - -A pyproject.toml is included to enable PEP 517 and PEP 518 compatibility and declares the requirements necessary to build the project on Setuptools (a minimum version compatible with setup.cfg declarative config). - -The setup.cfg file implements the following features: - -- Assumes universal wheel for release -- Advertises the project's LICENSE file (MIT by default) -- Reads the README.rst file into the long description -- Some common Trove classifiers -- Includes all packages discovered in the repo -- Data files in the package are also included (not just Python files) -- Declares the required Python versions -- Declares install requirements (empty by default) -- Declares setup requirements for legacy environments -- Supplies two 'extras': - - testing: requirements for running tests - - docs: requirements for building docs - - these extras split the declaration into "upstream" (requirements as declared by the skeleton) and "local" (those specific to the local project); these markers help avoid merge conflicts -- Placeholder for defining entry points - -Additionally, the setup.py file declares `use_scm_version` which relies on [setuptools_scm](https://pypi.org/project/setuptools_scm) to do two things: - -- derive the project version from SCM tags -- ensure that all files committed to the repo are automatically included in releases - -## Running Tests - -The skeleton assumes the developer has [tox](https://pypi.org/project/tox) installed. The developer is expected to run `tox` to run tests on the current Python version using [pytest](https://pypi.org/project/pytest). - -Other environments (invoked with `tox -e {name}`) supplied include: - - - a `docs` environment to build the documentation - - a `release` environment to publish the package to PyPI - -A pytest.ini is included to define common options around running tests. In particular: - -- rely on default test discovery in the current directory -- avoid recursing into common directories not containing tests -- run doctests on modules and invoke Flake8 tests -- in doctests, allow Unicode literals and regular literals to match, allowing for doctests to run on Python 2 and 3. Also enable ELLIPSES, a default that would be undone by supplying the prior option. -- filters out known warnings caused by libraries/functionality included by the skeleton - -Relies on a .flake8 file to correct some default behaviors: - -- disable mutually incompatible rules W503 and W504 -- support for Black format - -## Continuous Integration - -The project is pre-configured to run Continuous Integration tests. - -### Github Actions - -[Github Actions](https://docs.github.com/en/free-pro-team@latest/actions) are the preferred provider as they provide free, fast, multi-platform services with straightforward configuration. Configured in `.github/workflows`. - -Features include: -- test against multiple Python versions -- run on late (and updated) platform versions -- automated releases of tagged commits -- [automatic merging of PRs](https://github.com/marketplace/actions/merge-pull-requests) (requires [protecting branches with required status checks](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/enabling-required-status-checks), [not possible through API](https://github.community/t/set-all-status-checks-to-be-required-as-branch-protection-using-the-github-api/119493)) - - -### Continuous Deployments - -In addition to running tests, an additional publish stage is configured to automatically release tagged commits to PyPI using [API tokens](https://pypi.org/help/#apitoken). The release process expects an authorized token to be configured with each Github project (or org) `PYPI_TOKEN` [secret](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets). Example: - -``` -pip-run -q jaraco.develop -- -m jaraco.develop.add-github-secrets -``` - -## Building Documentation - -Documentation is automatically built by [Read the Docs](https://readthedocs.org) when the project is registered with it, by way of the .readthedocs.yml file. To test the docs build manually, a tox env may be invoked as `tox -e docs`. Both techniques rely on the dependencies declared in `setup.cfg/options.extras_require.docs`. - -In addition to building the Sphinx docs scaffolded in `docs/`, the docs build a `history.html` file that first injects release dates and hyperlinks into the CHANGES.rst before incorporating it as history in the docs. - -## Cutting releases - -By default, tagged commits are released through the continuous integration deploy stage. - -Releases may also be cut manually by invoking the tox environment `release` with the PyPI token set as the TWINE_PASSWORD: - -``` -TWINE_PASSWORD={token} tox -e release -``` From 8698127dbd17b47d1d07e35bee3725fecb69670b Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 26 Apr 2021 04:10:20 +0200 Subject: [PATCH 398/447] Make sphinx fail on any warnings (#36) This change adds `nitpicky=True` (which is an equivalent of `-n`) to make Sphinx emit warnings for any references to non-existing targets. Then, it adds `-W` to make it fail whenever a single warning is seen. Finally, `--keep-going` allows Sphinx to print out all the warnings before exiting instead of showing just one and bailing. Resolves #29 Refs: * https://www.sphinx-doc.org/en/master/man/sphinx-build.html#cmdoption-sphinx-build-n * https://www.sphinx-doc.org/en/master/man/sphinx-build.html#cmdoption-sphinx-build-W * https://www.sphinx-doc.org/en/master/man/sphinx-build.html#cmdoption-sphinx-build-keep-going --- docs/conf.py | 3 +++ tox.ini | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 433d185d..f65d1faa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,3 +24,6 @@ ], ) } + +# Be strict about any broken references: +nitpicky = True diff --git a/tox.ini b/tox.ini index a9a50b01..69848905 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ extras = testing changedir = docs commands = - python -m sphinx . {toxinidir}/build/html + python -m sphinx -W --keep-going . {toxinidir}/build/html [testenv:release] skip_install = True From 4a734d4841b0ad5fddad3c2524e512f608c82d74 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 20 May 2021 14:01:53 -0400 Subject: [PATCH 399/447] Test on Python 3.10 --- .github/workflows/main.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6a8ff006..7d6b455b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,7 +6,10 @@ jobs: test: strategy: matrix: - python: [3.6, 3.8, 3.9] + python: + - 3.6 + - 3.9 + - 3.10.0-alpha - 3.10.99 platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: From 1b165200642e74a4c2acebf7fedb28e732a17881 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 26 May 2021 10:40:46 -0400 Subject: [PATCH 400/447] Remove setup_requires, obviated by build-requires in pyproject.toml. --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 52876d55..e768c6b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,7 +19,6 @@ packages = find_namespace: include_package_data = true python_requires = >=3.6 install_requires = -setup_requires = setuptools_scm[toml] >= 3.4.1 [options.packages.find] exclude = From 85d08db3ef3811bd208995254e7e9c9658cf710d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 3 Jun 2021 19:55:51 -0400 Subject: [PATCH 401/447] Suppress deprecation warnings in flake8 and packaging.tags. Ref pypa/packaging#433. --- pytest.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pytest.ini b/pytest.ini index 6bf69af1..31b114fd 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,3 +5,7 @@ doctest_optionflags=ALLOW_UNICODE ELLIPSIS # workaround for warning pytest-dev/pytest#6178 junit_family=xunit2 filterwarnings= + # Suppress deprecation warning in flake8 + ignore:SelectableGroups dict interface is deprecated::flake8 + # Suppress deprecation warning in pypa/packaging#433 + ignore:The distutils package is deprecated::packaging.tags From 5a8384e53c59a886f982739c02572732afa76c7f Mon Sep 17 00:00:00 2001 From: Brian Rutledge Date: Sat, 12 Jun 2021 10:10:07 -0400 Subject: [PATCH 402/447] Use shutil for rmtree --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 69848905..3ca2af38 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,6 @@ skip_install = True deps = build twine>=3 - path jaraco.develop>=7.1 passenv = TWINE_PASSWORD @@ -34,7 +33,7 @@ passenv = setenv = TWINE_USERNAME = {env:TWINE_USERNAME:__token__} commands = - python -c "import path; path.Path('dist').rmtree_p()" + python -c "import shutil; shutil.rmtree('dist', ignore_errors=True)" python -m build python -m twine upload dist/* python -m jaraco.develop.create-github-release From 14787e69e793d68c8ac17f010dc45891ee0a492c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 16 Jul 2021 09:03:21 -0400 Subject: [PATCH 403/447] Rely on setuptools 56 and drop the explicit mention of the license file in favor of simple discovery. --- pyproject.toml | 2 +- setup.cfg | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b6ebc0be..28bd7883 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.4.1"] +requires = ["setuptools>=56", "wheel", "setuptools_scm[toml]>=3.4.1"] build-backend = "setuptools.build_meta" [tool.black] diff --git a/setup.cfg b/setup.cfg index e768c6b3..53387b60 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,4 @@ [metadata] -license_files = - LICENSE name = skeleton author = Jason R. Coombs author_email = jaraco@jaraco.com From 212e995cd366010a8c372ea2fedfbb8be471e5cb Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 16 Jul 2021 09:07:59 -0400 Subject: [PATCH 404/447] Remove workaround for python/typed_ast#156. --- setup.cfg | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 53387b60..80fd268f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,12 +32,10 @@ testing = pytest-checkdocs >= 2.4 pytest-flake8 # python_implementation: workaround for jaraco/skeleton#22 - # python_version: workaround for python/typed_ast#156 - pytest-black >= 0.3.7; python_implementation != "PyPy" and python_version < "3.10" + pytest-black >= 0.3.7; python_implementation != "PyPy" pytest-cov # python_implementation: workaround for jaraco/skeleton#22 - # python_version: workaround for python/typed_ast#156 - pytest-mypy; python_implementation != "PyPy" and python_version < "3.10" + pytest-mypy; python_implementation != "PyPy" pytest-enabler >= 1.0.1 # local From 498b965a805224420c8cde5969bf342a41766227 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 16 Jul 2021 09:19:05 -0400 Subject: [PATCH 405/447] Use line continuations to indicate which exclusions are for which workarounds. --- setup.cfg | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 80fd268f..69eb0ee6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,11 +31,13 @@ testing = pytest >= 4.6 pytest-checkdocs >= 2.4 pytest-flake8 - # python_implementation: workaround for jaraco/skeleton#22 - pytest-black >= 0.3.7; python_implementation != "PyPy" + pytest-black >= 0.3.7; \ + # workaround for jaraco/skeleton#22 + python_implementation != "PyPy" pytest-cov - # python_implementation: workaround for jaraco/skeleton#22 - pytest-mypy; python_implementation != "PyPy" + pytest-mypy; \ + # workaround for jaraco/skeleton#22 + python_implementation != "PyPy" pytest-enabler >= 1.0.1 # local From 719a7ced8a1713b7fe94d842a8f6fec7425b8a0a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 21 Jul 2021 17:33:29 -0400 Subject: [PATCH 406/447] Remove blacken docs as it cannot honor Python's default repr. Ref asottile/blacken-docs#62. --- .pre-commit-config.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c15ab0c9..f66bf563 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,8 +3,3 @@ repos: rev: 20.8b1 hooks: - id: black - -- repo: https://github.com/asottile/blacken-docs - rev: v1.9.1 - hooks: - - id: blacken-docs From a76a548d0f25947d2594d36b784d029a6ada977f Mon Sep 17 00:00:00 2001 From: Alan Fregtman <941331+darkvertex@users.noreply.github.com> Date: Mon, 26 Jul 2021 10:55:08 -0400 Subject: [PATCH 407/447] .editorconfig: Set max_line_length to 88 for Python files. --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index 6385b573..b8aeea17 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,6 +9,7 @@ end_of_line = lf [*.py] indent_style = space +max_line_length = 88 [*.{yml,yaml}] indent_style = space From 8ea55f2fb26bd77997f0e9435bab2d41376a76d4 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 17 Sep 2021 21:23:38 -0400 Subject: [PATCH 408/447] Add intersphinx mappings for Python to prevent spurious nitpicky failures. Fixes jaraco/skeleton#51. --- docs/conf.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index f65d1faa..4ae74093 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,3 +27,10 @@ # Be strict about any broken references: nitpicky = True + +# Include Python intersphinx mapping to prevent failures +# jaraco/skeleton#51 +extensions += ['sphinx.ext.intersphinx'] +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), +} From dc43378c8accd85321b42e3fe69fcb87e5266006 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 5 Oct 2021 22:45:21 -0400 Subject: [PATCH 409/447] Test on Python 3.10 (final). --- .github/workflows/main.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7d6b455b..6aad7f11 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,8 +9,11 @@ jobs: python: - 3.6 - 3.9 - - 3.10.0-alpha - 3.10.99 - platform: [ubuntu-latest, macos-latest, windows-latest] + - "3.10" + platform: + - ubuntu-latest + - macos-latest + - windows-latest runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 @@ -34,7 +37,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.9 + python-version: "3.10" - name: Install tox run: | python -m pip install tox From 5823e9ca9d242b733a5ff3c8e2c22e13ec0a4c01 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 7 Oct 2021 19:52:04 -0400 Subject: [PATCH 410/447] Rely on pytest 6 and drop workaround for pytest-dev/pytest#6178. --- pytest.ini | 2 -- setup.cfg | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pytest.ini b/pytest.ini index 31b114fd..9ecdba49 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,8 +2,6 @@ norecursedirs=dist build .tox .eggs addopts=--doctest-modules doctest_optionflags=ALLOW_UNICODE ELLIPSIS -# workaround for warning pytest-dev/pytest#6178 -junit_family=xunit2 filterwarnings= # Suppress deprecation warning in flake8 ignore:SelectableGroups dict interface is deprecated::flake8 diff --git a/setup.cfg b/setup.cfg index 69eb0ee6..0f7d652d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,7 +28,7 @@ exclude = [options.extras_require] testing = # upstream - pytest >= 4.6 + pytest >= 6 pytest-checkdocs >= 2.4 pytest-flake8 pytest-black >= 0.3.7; \ From aae281a9ff6c9a1fa9daad82c79457e8770a1c7e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 22 Oct 2021 14:19:58 -0400 Subject: [PATCH 411/447] Remove wheel from build requirements. It's implied for wheel builds. Ref pypa/setuptools#1498. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 28bd7883..190b3551 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=56", "wheel", "setuptools_scm[toml]>=3.4.1"] +requires = ["setuptools>=56", "setuptools_scm[toml]>=3.4.1"] build-backend = "setuptools.build_meta" [tool.black] From 0019b0af43b9e381e2f0b14753d1bf40ce204490 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 24 Nov 2021 20:08:49 -0500 Subject: [PATCH 412/447] Require Python 3.7 or later. --- .github/workflows/main.yml | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6aad7f11..5424298d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: python: - - 3.6 + - 3.7 - 3.9 - "3.10" platform: diff --git a/setup.cfg b/setup.cfg index 0f7d652d..bd1da7a2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,7 @@ classifiers = [options] packages = find_namespace: include_package_data = true -python_requires = >=3.6 +python_requires = >=3.7 install_requires = [options.packages.find] From eca1c4ca6e104c8add280c721cbb365196f55ac7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 24 Nov 2021 20:38:46 -0500 Subject: [PATCH 413/447] Remove filtered warnings, addressed upstream. --- pytest.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/pytest.ini b/pytest.ini index 9ecdba49..ec965b24 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,5 +5,3 @@ doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= # Suppress deprecation warning in flake8 ignore:SelectableGroups dict interface is deprecated::flake8 - # Suppress deprecation warning in pypa/packaging#433 - ignore:The distutils package is deprecated::packaging.tags From 4f9825dafa8d13a5f8b8bd8eb8bfc6414329cb18 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 1 Feb 2022 04:09:17 -0500 Subject: [PATCH 414/447] Update badge year --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a3e1b740..c82c6429 100644 --- a/README.rst +++ b/README.rst @@ -17,5 +17,5 @@ .. .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest .. :target: https://skeleton.readthedocs.io/en/latest/?badge=latest -.. image:: https://img.shields.io/badge/skeleton-2021-informational +.. image:: https://img.shields.io/badge/skeleton-2022-informational :target: https://blog.jaraco.com/skeleton From 7e01b721c237ee4947e3b9d6e56bb03a028f3f6a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 1 Feb 2022 04:11:54 -0500 Subject: [PATCH 415/447] Remove setup.py, no longer needed. --- setup.py | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index bac24a43..00000000 --- a/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python - -import setuptools - -if __name__ == "__main__": - setuptools.setup() From 8949d1a1169c9271ceb8aab3f1deea9d82e2fa0d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 4 Feb 2022 21:45:52 -0500 Subject: [PATCH 416/447] Add exclusions for pytest 7 deprecations in plugins. Fixes jaraco/skeleton#57. --- pytest.ini | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pytest.ini b/pytest.ini index ec965b24..52f19bea 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,3 +5,14 @@ doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= # Suppress deprecation warning in flake8 ignore:SelectableGroups dict interface is deprecated::flake8 + + # shopkeep/pytest-black#55 + ignore: is not using a cooperative constructor:pytest.PytestDeprecationWarning + ignore:The \(fspath. py.path.local\) argument to BlackItem is deprecated.:pytest.PytestRemovedIn8Warning + + # tholo/pytest-flake8#83 + ignore: is not using a cooperative constructor:pytest.PytestDeprecationWarning + ignore:The \(fspath. py.path.local\) argument to Flake8Item is deprecated.:pytest.PytestRemovedIn8Warning + + # dbader/pytest-mypy#131 + ignore:The \(fspath. py.path.local\) argument to MypyFile is deprecated.:pytest.PytestRemovedIn8Warning From badffe9af9b79dff781f6768bcf48fbd8abd0945 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 5 Feb 2022 14:29:09 -0500 Subject: [PATCH 417/447] Use the parent category PytestDeprecationWarning, which is available on older pytest versions. Fixes jaraco/skeleton#57. --- pytest.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pytest.ini b/pytest.ini index 52f19bea..cbbe3b15 100644 --- a/pytest.ini +++ b/pytest.ini @@ -8,11 +8,11 @@ filterwarnings= # shopkeep/pytest-black#55 ignore: is not using a cooperative constructor:pytest.PytestDeprecationWarning - ignore:The \(fspath. py.path.local\) argument to BlackItem is deprecated.:pytest.PytestRemovedIn8Warning + ignore:The \(fspath. py.path.local\) argument to BlackItem is deprecated.:pytest.PytestDeprecationWarning # tholo/pytest-flake8#83 ignore: is not using a cooperative constructor:pytest.PytestDeprecationWarning - ignore:The \(fspath. py.path.local\) argument to Flake8Item is deprecated.:pytest.PytestRemovedIn8Warning + ignore:The \(fspath. py.path.local\) argument to Flake8Item is deprecated.:pytest.PytestDeprecationWarning # dbader/pytest-mypy#131 - ignore:The \(fspath. py.path.local\) argument to MypyFile is deprecated.:pytest.PytestRemovedIn8Warning + ignore:The \(fspath. py.path.local\) argument to MypyFile is deprecated.:pytest.PytestDeprecationWarning From 96ea56305df99a3c13334d42ea45f779cab2c505 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 10 Feb 2022 20:36:16 -0500 Subject: [PATCH 418/447] Bump pytest-mypy and remove workaround for dbader/pytest-mypy#131. --- pytest.ini | 3 --- setup.cfg | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pytest.ini b/pytest.ini index cbbe3b15..b6880c88 100644 --- a/pytest.ini +++ b/pytest.ini @@ -13,6 +13,3 @@ filterwarnings= # tholo/pytest-flake8#83 ignore: is not using a cooperative constructor:pytest.PytestDeprecationWarning ignore:The \(fspath. py.path.local\) argument to Flake8Item is deprecated.:pytest.PytestDeprecationWarning - - # dbader/pytest-mypy#131 - ignore:The \(fspath. py.path.local\) argument to MypyFile is deprecated.:pytest.PytestDeprecationWarning diff --git a/setup.cfg b/setup.cfg index bd1da7a2..1b048af5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,7 +35,7 @@ testing = # workaround for jaraco/skeleton#22 python_implementation != "PyPy" pytest-cov - pytest-mypy; \ + pytest-mypy >= 0.9.1; \ # workaround for jaraco/skeleton#22 python_implementation != "PyPy" pytest-enabler >= 1.0.1 From a9ea801a43fc62a569cf60e1c28e477ba510d8a0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 10 Feb 2022 21:58:57 -0500 Subject: [PATCH 419/447] Require jaraco.packaging 9 adding compatibility for projects with no setup.py file. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1b048af5..3b7ac309 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,7 +45,7 @@ testing = docs = # upstream sphinx - jaraco.packaging >= 8.2 + jaraco.packaging >= 9 rst.linker >= 1.9 # local From f22eb5b60adbe158e458614ea0380a9071c39347 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Sat, 12 Feb 2022 09:50:31 +0000 Subject: [PATCH 420/447] Ignore flake8/black warnings with pytest 7.0.1 (jaraco/skeleton#58) --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest.ini b/pytest.ini index b6880c88..80e98cc9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -9,7 +9,9 @@ filterwarnings= # shopkeep/pytest-black#55 ignore: is not using a cooperative constructor:pytest.PytestDeprecationWarning ignore:The \(fspath. py.path.local\) argument to BlackItem is deprecated.:pytest.PytestDeprecationWarning + ignore:BlackItem is an Item subclass and should not be a collector:pytest.PytestWarning # tholo/pytest-flake8#83 ignore: is not using a cooperative constructor:pytest.PytestDeprecationWarning ignore:The \(fspath. py.path.local\) argument to Flake8Item is deprecated.:pytest.PytestDeprecationWarning + ignore:Flake8Item is an Item subclass and should not be a collector:pytest.PytestWarning From 04fe68a96ee8e3d3ca521b4abbfe53203063f9d9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 19 Feb 2022 21:14:39 -0500 Subject: [PATCH 421/447] Ran pre-commit autoupdate --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f66bf563..edf6f55f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ repos: - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 22.1.0 hooks: - id: black From 1a6b828304e7a8896b55d9ebf83f481ba7ebd568 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Fri, 22 Apr 2022 17:43:46 +0200 Subject: [PATCH 422/447] Inject check job into CI workflow as ultimate flag (#55) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a job that is able to accurately signal whether all the expectations of the required jobs to succeed are met. This job can then be used as a source of truth for judging whether "CI passes" and can be used in the branch protection. It also plays a role of a convenient "gate" — this is the only job that would have to be listed in the branch protection as opposed to listing every single job name generated by the test matrix (and they all have different names — it's not possible to just select one `test` job name). Ref: https://github.com/re-actors/alls-green#why --- .github/workflows/main.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5424298d..b54fd6a1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,8 +27,23 @@ jobs: - name: Run tests run: tox + check: # This job does nothing and is only used for the branch protection + if: always() + + needs: + - test + + runs-on: ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} + release: - needs: test + needs: + - check if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') runs-on: ubuntu-latest From 10bf1b1fb9e09e9836bea9e2edec620cd9eea7f9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 4 Jun 2022 21:37:40 -0400 Subject: [PATCH 423/447] Add Python 3.11 into the matrix using workaround from actions/setup-python#213. Drop 3.9 from matrix for efficiency. --- .github/workflows/main.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b54fd6a1..6468ee0d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,9 +7,11 @@ jobs: strategy: matrix: python: - - 3.7 - - 3.9 - - "3.10" + # Build on pre-releases until stable, then stable releases. + # actions/setup-python#213 + - ~3.7.0-0 + - ~3.10.0-0 + - ~3.11.0-0 platform: - ubuntu-latest - macos-latest From a4f5b769793af19f7b858816889c1bf026f55f5c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 5 Jun 2022 04:47:15 +0300 Subject: [PATCH 424/447] Update base URL for PEPs (#61) --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 4ae74093..319b1384 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,7 @@ ), dict( pattern=r'PEP[- ](?P\d+)', - url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/', + url='https://peps.python.org/pep-{pep_number:0>4}/', ), ], ) From 74f337fec4c233b3a6750fa64b61d03c189d9416 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Sun, 5 Jun 2022 02:50:24 +0100 Subject: [PATCH 425/447] Update Github actions to v3 (#62) --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6468ee0d..948da052 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,9 +18,9 @@ jobs: - windows-latest runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python }} - name: Install tox @@ -50,9 +50,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: "3.10" - name: Install tox From e719f86c138a750f0c4599cd01cb8067b1ca95c8 Mon Sep 17 00:00:00 2001 From: wim glenn Date: Sun, 5 Jun 2022 15:01:02 -0500 Subject: [PATCH 426/447] exclude build env from cov reporting (#60) * Update .coveragerc * Keep whitespace consistent. Co-authored-by: Jason R. Coombs --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 6a34e662..01164f62 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,6 +2,7 @@ omit = # leading `*/` for pytest-dev/pytest-cov#456 */.tox/* + */pep517-build-env-* [report] show_missing = True From 6dcd157a7057ec8e1f1f6afebe2115f55df4aaed Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 15 Jun 2022 20:57:40 -0400 Subject: [PATCH 427/447] Prefer spaces for rst. Fixes jaraco/skeleton#64. --- .editorconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.editorconfig b/.editorconfig index b8aeea17..304196f8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,6 @@ max_line_length = 88 [*.{yml,yaml}] indent_style = space indent_size = 2 + +[*.rst] +indent_style = space From 2678a7e82d581c07691575d90cd255b64ee63a27 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 22 Jun 2022 15:56:54 -0400 Subject: [PATCH 428/447] Honor PEP 518 with pytest-enabler. --- pyproject.toml | 8 ++++---- setup.cfg | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 190b3551..60de2424 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,14 +7,14 @@ skip-string-normalization = true [tool.setuptools_scm] -[pytest.enabler.black] +[tool.pytest-enabler.black] addopts = "--black" -[pytest.enabler.mypy] +[tool.pytest-enabler.mypy] addopts = "--mypy" -[pytest.enabler.flake8] +[tool.pytest-enabler.flake8] addopts = "--flake8" -[pytest.enabler.cov] +[tool.pytest-enabler.cov] addopts = "--cov" diff --git a/setup.cfg b/setup.cfg index 3b7ac309..baa37e5e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,7 +38,7 @@ testing = pytest-mypy >= 0.9.1; \ # workaround for jaraco/skeleton#22 python_implementation != "PyPy" - pytest-enabler >= 1.0.1 + pytest-enabler >= 1.3 # local From fea1e7cdd57d330f22ac54512ae2df19083c6ec7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 11 Jul 2022 18:53:07 -0400 Subject: [PATCH 429/447] Ran pre-commit autoupdate --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index edf6f55f..af502010 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ repos: - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.6.0 hooks: - id: black From 325916c8240b8b3c7c41f24b664ca591e8555ea9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 29 Jul 2022 10:12:46 -0400 Subject: [PATCH 430/447] Use '-dev' for every Python version. Ref actions/setup-python#213. --- .github/workflows/main.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 948da052..de49ba8a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,11 +7,9 @@ jobs: strategy: matrix: python: - # Build on pre-releases until stable, then stable releases. - # actions/setup-python#213 - - ~3.7.0-0 - - ~3.10.0-0 - - ~3.11.0-0 + - 3.7 + - '3.10' + - '3.11' platform: - ubuntu-latest - macos-latest @@ -22,7 +20,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v3 with: - python-version: ${{ matrix.python }} + python-version: ${{ matrix.python }}-dev - name: Install tox run: | python -m pip install tox From 424717b9e9f7c66379e809eb4e35daae827a1533 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 29 Jul 2022 10:18:19 -0400 Subject: [PATCH 431/447] Use Python 3.11 for cutting releases. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index de49ba8a..3ce62d92 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,7 +52,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v3 with: - python-version: "3.10" + python-version: "3.11-dev" - name: Install tox run: | python -m pip install tox From c64902b8cafa8062398ef173278a21b042b03a77 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 30 Jul 2022 19:26:15 -0400 Subject: [PATCH 432/447] Pin flake8. Workaround for tholo/pytest-flake8#87. --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index baa37e5e..1ab93501 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,8 @@ testing = pytest >= 6 pytest-checkdocs >= 2.4 pytest-flake8 + # workaround for tholo/pytest-flake8#87 + flake8 < 5 pytest-black >= 0.3.7; \ # workaround for jaraco/skeleton#22 python_implementation != "PyPy" From abcc15683d3abe229a0e0d07f1afa05a24e2ef8c Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 18 Aug 2022 16:06:12 -0400 Subject: [PATCH 433/447] Update to setup-python v4. Fixes jaraco/skeleton#65. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ce62d92..d17b64d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }}-dev - name: Install tox From 47c2cb324e20f784289496ef3a7b19a1cd23d196 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 18 Aug 2022 21:42:40 -0400 Subject: [PATCH 434/447] Also update release to v4 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d17b64d6..63fa1e8e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -50,7 +50,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: "3.11-dev" - name: Install tox From efccb66ef6326b2fc8648ff7c4338b2ca7427b6a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 28 Aug 2022 10:25:18 -0400 Subject: [PATCH 435/447] =?UTF-8?q?=F0=9F=91=B9=20Feed=20the=20hobgoblins?= =?UTF-8?q?=20(delint).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- calendra/africa/south_africa.py | 2 +- calendra/core.py | 2 +- calendra/usa/core.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/calendra/africa/south_africa.py b/calendra/africa/south_africa.py index 30606236..5f10ee5f 100644 --- a/calendra/africa/south_africa.py +++ b/calendra/africa/south_africa.py @@ -24,7 +24,7 @@ def get_easter_monday_or_family_day(self, year): label = "Family Day" return (self.get_easter_monday(year), label) - def get_fixed_holidays(self, year): + def get_fixed_holidays(self, year): # noqa: C901 days = super().get_fixed_holidays(year) if year >= 1990: days.append((date(year, 3, 21), 'Human Rights Day')) diff --git a/calendra/core.py b/calendra/core.py index 88e6347a..5526664b 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -1189,7 +1189,7 @@ class IslamicMixin(SeriesShiftMixin, CalverterMixin): include_laylat_al_qadr = False include_nuzul_al_quran = False - def get_islamic_holidays(self): + def get_islamic_holidays(self): # noqa: C901 """Return a list of Islamic (month, day, label) for islamic holidays. Please take note that these dates must be expressed using the Islamic Calendar""" diff --git a/calendra/usa/core.py b/calendra/usa/core.py index 4366b56d..9df0d050 100644 --- a/calendra/usa/core.py +++ b/calendra/usa/core.py @@ -226,7 +226,7 @@ def get_national_memorial_day(self, year): indication="Last Monday in May", ) - def get_variable_days(self, year): + def get_variable_days(self, year): # noqa: C901 # usual variable days days = super().get_variable_days(year) From 9e3ebe4406a8b87e3a54ef9c0543fd998c400ee5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Sep 2022 21:16:40 -0400 Subject: [PATCH 436/447] Move holiday implementation to its own module to simplify merging. --- calendra/core.py | 98 +------------------------------------------- calendra/holiday.py | 99 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 97 deletions(-) create mode 100644 calendra/holiday.py diff --git a/calendra/core.py b/calendra/core.py index 5526664b..3bc9a1ee 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -5,7 +5,6 @@ from os.path import isdir import warnings -import itertools from calendar import monthrange from datetime import date, timedelta, datetime from typing import Optional, List @@ -14,13 +13,13 @@ from dateutil import easter from lunardate import LunarDate from dateutil import relativedelta as rd -from more_itertools import recipes from .exceptions import ( UnsupportedDateType, CalendarError, ICalExportRangeError, ICalExportTargetPathError ) from . import __version__ +from .holiday import Holiday MON, TUE, WED, THU, FRI, SAT, SUN = range(7) @@ -67,101 +66,6 @@ def daterange(start, end): day += timedelta(days=1) -class Holiday(date): - """ - A named holiday with an indicated date, name, and additional keyword - attributes. - - >>> nyd = Holiday(date(2014, 1, 1), "New year") - - But if New Year's Eve is also a holiday, and it too falls on a weekend, - many calendars will have that holiday fall back to the previous friday: - - >>> nye = Holiday(date(2014, 12, 31), "New year's eve", - ... observance_shift=dict(weekday=rd.FR(-1))) - - For compatibility, a Holiday may be treated like a tuple of (date, label) - - >>> nyd[0] == date(2014, 1, 1) - True - >>> nyd[1] - 'New year' - >>> d, label = nyd - """ - - def __new__(cls, date, *args, **kwargs): - return super().__new__( - cls, date.year, date.month, date.day) - - def __init__(self, date, name='Holiday', **kwargs): - self.name = name - vars(self).update(kwargs) - - def __getitem__(self, n): - """ - for compatibility as a two-tuple - """ - tp = self, self.name - return tp[n] - - def __iter__(self): - """ - for compatibility as a two-tuple - """ - tp = self, self.name - return iter(tp) - - def replace(self, *args, **kwargs): - replaced = super().replace(*args, **kwargs) - vars(replaced).update(vars(self)) - return replaced - - def __add__(self, other): - orig = date(self.year, self.month, self.day) - return Holiday(orig + other, **vars(self)) - - def __sub__(self, other): - orig = date(self.year, self.month, self.day) - return Holiday(orig - other, **vars(self)) - - def nearest_weekday(self, calendar): - """ - Return the nearest weekday to self. - """ - weekend_days = calendar.get_weekend_days() - deltas = (timedelta(n) for n in itertools.count()) - candidates = recipes.flatten( - (self - delta, self + delta) - for delta in deltas - ) - matches = ( - day for day in candidates - if day.weekday() not in weekend_days - ) - return next(matches) - - @classmethod - def _from_fixed_definition(cls, item): - """For backward compatibility, load Holiday object from an item of - FIXED_HOLIDAYS class property, which might be just a tuple of - month, day, label. - """ - if isinstance(item, tuple): - month, day, label = item - any_year = 2000 - item = Holiday(date(any_year, month, day), label) - return item - - @classmethod - def _from_resolved_definition(cls, item): - """For backward compatibility, load Holiday object from a two-tuple - or existing Holiday instance. - """ - if isinstance(item, tuple): - item = Holiday(*item) - return item - - class CoreCalendar: FIXED_HOLIDAYS = () diff --git a/calendra/holiday.py b/calendra/holiday.py new file mode 100644 index 00000000..ea114e8d --- /dev/null +++ b/calendra/holiday.py @@ -0,0 +1,99 @@ +import itertools +from datetime import date, timedelta +from more_itertools import recipes + + +class Holiday(date): + """ + A named holiday with an indicated date, name, and additional keyword + attributes. + + >>> nyd = Holiday(date(2014, 1, 1), "New year") + + But if New Year's Eve is also a holiday, and it too falls on a weekend, + many calendars will have that holiday fall back to the previous friday: + + >>> from dateutil import relativedelta as rd + >>> nye = Holiday(date(2014, 12, 31), "New year's eve", + ... observance_shift=dict(weekday=rd.FR(-1))) + + For compatibility, a Holiday may be treated like a tuple of (date, label) + + >>> nyd[0] == date(2014, 1, 1) + True + >>> nyd[1] + 'New year' + >>> d, label = nyd + """ + + def __new__(cls, date, *args, **kwargs): + return super().__new__( + cls, date.year, date.month, date.day) + + def __init__(self, date, name='Holiday', **kwargs): + self.name = name + vars(self).update(kwargs) + + def __getitem__(self, n): + """ + for compatibility as a two-tuple + """ + tp = self, self.name + return tp[n] + + def __iter__(self): + """ + for compatibility as a two-tuple + """ + tp = self, self.name + return iter(tp) + + def replace(self, *args, **kwargs): + replaced = super().replace(*args, **kwargs) + vars(replaced).update(vars(self)) + return replaced + + def __add__(self, other): + orig = date(self.year, self.month, self.day) + return Holiday(orig + other, **vars(self)) + + def __sub__(self, other): + orig = date(self.year, self.month, self.day) + return Holiday(orig - other, **vars(self)) + + def nearest_weekday(self, calendar): + """ + Return the nearest weekday to self. + """ + weekend_days = calendar.get_weekend_days() + deltas = (timedelta(n) for n in itertools.count()) + candidates = recipes.flatten( + (self - delta, self + delta) + for delta in deltas + ) + matches = ( + day for day in candidates + if day.weekday() not in weekend_days + ) + return next(matches) + + @classmethod + def _from_fixed_definition(cls, item): + """For backward compatibility, load Holiday object from an item of + FIXED_HOLIDAYS class property, which might be just a tuple of + month, day, label. + """ + if isinstance(item, tuple): + month, day, label = item + any_year = 2000 + item = Holiday(date(any_year, month, day), label) + return item + + @classmethod + def _from_resolved_definition(cls, item): + """For backward compatibility, load Holiday object from a two-tuple + or existing Holiday instance. + """ + if isinstance(item, tuple): + item = Holiday(*item) + return item From d279bb022d3d1caff847e4092305876dc038e2a7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Sep 2022 21:22:45 -0400 Subject: [PATCH 437/447] Move SeriesShiftMixin to holiday module for easier merging. --- calendra/core.py | 60 +-------------------------------------------- calendra/holiday.py | 59 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 59 deletions(-) diff --git a/calendra/core.py b/calendra/core.py index 3bc9a1ee..86010db2 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -7,7 +7,6 @@ from calendar import monthrange from datetime import date, timedelta, datetime -from typing import Optional, List from calverter import Calverter from dateutil import easter @@ -19,7 +18,7 @@ ICalExportRangeError, ICalExportTargetPathError ) from . import __version__ -from .holiday import Holiday +from .holiday import Holiday, SeriesShiftMixin MON, TUE, WED, THU, FRI, SAT, SUN = range(7) @@ -589,63 +588,6 @@ def get_variable_days(self, year): return days -class SeriesShiftMixin: - """ - "Series" holidays like the two Islamic Eid's or Chinese Spring Festival span - multiple days. If one of these days encounters a non-zero observance_shift, - we need to apply that shift to all subsequent members of the series. - - Packagin as a standalone Mixin ensure that the logic can be applied as - needed *after* any default shift is applied. - """ - series_requiring_shifts: Optional[List[str]] = None - """ - A list of all holiday labels that require series shifting to be applied. - """ - - def get_calendar_holidays(self, year): - """ - The point at which any shift occurs is year-specific. - """ - days = super().get_calendar_holidays(year) - series_shift = {series: None for series in self.series_requiring_shifts} - holidays = [] - for holiday, label in days: - # - # Make a year-specific copy in case we have to attach a shift. - # - holiday = Holiday(holiday, label) - # - # For either Eid series, apply the shift to all days in the - # series after the first shift. - # - if label in series_shift: - shifted = self.get_observed_date(holiday) - if series_shift[holiday.name] is None and shifted.day != holiday.day: - - def observance_shift_for_series(holiday, calendar): - """ - Taking an existing holiday, return a 'shifted' day based - on delta in the current year's closure. - """ - return holiday + delta - - delta = date(shifted.year, shifted.month, shifted.day) - \ - date(holiday.year, holiday.month, holiday.day) - # - # Learn the observance_shift for all subsequent days in the - # series. - # - series_shift[holiday.name] = observance_shift_for_series - elif series_shift[holiday.name] is not None: - # - # Apply the learned observance_shift. - # - holiday.observance_shift = series_shift[holiday.name] - holidays.append(holiday) - return holidays - - class ChristianMixin: EASTER_METHOD = None # to be assigned in the inherited mixin include_epiphany = False diff --git a/calendra/holiday.py b/calendra/holiday.py index ea114e8d..77e0c596 100644 --- a/calendra/holiday.py +++ b/calendra/holiday.py @@ -1,5 +1,7 @@ import itertools from datetime import date, timedelta +from typing import Optional, List + from more_itertools import recipes @@ -97,3 +99,60 @@ def _from_resolved_definition(cls, item): if isinstance(item, tuple): item = Holiday(*item) return item + + +class SeriesShiftMixin: + """ + "Series" holidays like the two Islamic Eid's or Chinese Spring Festival span + multiple days. If one of these days encounters a non-zero observance_shift, + we need to apply that shift to all subsequent members of the series. + + Packagin as a standalone Mixin ensure that the logic can be applied as + needed *after* any default shift is applied. + """ + series_requiring_shifts: Optional[List[str]] = None + """ + A list of all holiday labels that require series shifting to be applied. + """ + + def get_calendar_holidays(self, year): + """ + The point at which any shift occurs is year-specific. + """ + days = super().get_calendar_holidays(year) + series_shift = {series: None for series in self.series_requiring_shifts} + holidays = [] + for holiday, label in days: + # + # Make a year-specific copy in case we have to attach a shift. + # + holiday = Holiday(holiday, label) + # + # For either Eid series, apply the shift to all days in the + # series after the first shift. + # + if label in series_shift: + shifted = self.get_observed_date(holiday) + if series_shift[holiday.name] is None and shifted.day != holiday.day: + + def observance_shift_for_series(holiday, calendar): + """ + Taking an existing holiday, return a 'shifted' day based + on delta in the current year's closure. + """ + return holiday + delta + + delta = date(shifted.year, shifted.month, shifted.day) - \ + date(holiday.year, holiday.month, holiday.day) + # + # Learn the observance_shift for all subsequent days in the + # series. + # + series_shift[holiday.name] = observance_shift_for_series + elif series_shift[holiday.name] is not None: + # + # Apply the learned observance_shift. + # + holiday.observance_shift = series_shift[holiday.name] + holidays.append(holiday) + return holidays From 7958bc88a9050bfa698ed81eecea6ae89103b8bd Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Sep 2022 21:28:39 -0400 Subject: [PATCH 438/447] Reorder classes similar to workalendar for easier merging. --- calendra/core.py | 1662 +++++++++++++++++++++++----------------------- 1 file changed, 831 insertions(+), 831 deletions(-) diff --git a/calendra/core.py b/calendra/core.py index 86010db2..54f93a1e 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -65,1014 +65,1014 @@ def daterange(start, end): day += timedelta(days=1) -class CoreCalendar: +class ChristianMixin: + EASTER_METHOD = None # to be assigned in the inherited mixin + include_epiphany = False + include_clean_monday = False + include_annunciation = False + include_fat_tuesday = False + # Fat tuesday forced to `None` to make sure this value is always set + # We've seen that there was a wide variety of labels. + fat_tuesday_label = None + include_ash_wednesday = False + ash_wednesday_label = "Ash Wednesday" + include_palm_sunday = False + include_holy_thursday = False + include_good_friday = False + good_friday_label = "Good Friday" + include_easter_monday = False + include_easter_saturday = False + include_easter_sunday = False + include_all_saints = False + include_immaculate_conception = False + immaculate_conception_label = "Immaculate Conception" + include_christmas = True + include_christmas_eve = False + include_ascension = False + include_assumption = False + include_whit_sunday = False + whit_sunday_label = 'Whit Sunday' + include_whit_monday = False + whit_monday_label = 'Whit Monday' + include_corpus_christi = False + include_boxing_day = False + boxing_day_label = "Boxing Day" + include_all_souls = False - FIXED_HOLIDAYS = () - WEEKEND_DAYS = () - observance_shift = dict(weekday=rd.MO(1)) - """ - The shift for the observance of a holiday defined as keyword parameters to - a rd.relativedelta instance. - By default, holidays are shifted to the Monday following the weekend. - """ + def get_fat_tuesday(self, year): + if not self.fat_tuesday_label: + raise CalendarError( + "Improperly configured: please provide a " + "`fat_tuesday_label` value") + sunday = self.get_easter_sunday(year) + return sunday - timedelta(days=47) - def __init__(self): - self._holidays = {} + def get_ash_wednesday(self, year): + sunday = self.get_easter_sunday(year) + return sunday - timedelta(days=46) - @classproperty - def name(cls): - class_name = cls.__name__ - if cls.__doc__: - doc = cls.__doc__.split('\n') - doc = map(lambda s: s.strip(), doc) - return next(s for s in doc if s) - return class_name + def get_palm_sunday(self, year): + sunday = self.get_easter_sunday(year) + return sunday - timedelta(days=7) - def get_fixed_holidays(self, year): - """Return the fixed days according to the FIXED_HOLIDAYS class property - """ - fixed_holidays = map( - Holiday._from_fixed_definition, - self.FIXED_HOLIDAYS, - ) - return [day.replace(year=year) for day in fixed_holidays] + def get_holy_thursday(self, year): + "Return the date of the last thursday before easter" + sunday = self.get_easter_sunday(year) + return sunday - timedelta(days=3) - def get_variable_days(self, year): - return [] + def get_good_friday(self, year): + "Return the date of the last friday before easter" + sunday = self.get_easter_sunday(year) + return sunday - timedelta(days=2) - def get_calendar_holidays(self, year): - """Get calendar holidays. - If you want to override this, please make sure that it **must** return - a list of tuples (date, holiday_name).""" - return self.get_fixed_holidays(year) + self.get_variable_days(year) + def get_clean_monday(self, year): + "Return the clean monday date" + sunday = self.get_easter_sunday(year) + return sunday - timedelta(days=48) - def holidays(self, year=None): - """Computes holidays (non-working days) for a given year. - Return a 2-item tuple, composed of the date and a label.""" - if not year: - year = date.today().year + def get_easter_saturday(self, year): + "Return the Easter Saturday date" + sunday = self.get_easter_sunday(year) + return sunday - timedelta(days=1) - if year in self._holidays: - return self._holidays[year] + def get_easter_sunday(self, year): + "Return the date of the easter (sunday) -- following the easter method" + return easter.easter(year, self.EASTER_METHOD) - # Here we process the holiday specific calendar - days = self.get_calendar_holidays(year) - days = map(Holiday._from_resolved_definition, days) - temp_calendar = tuple(days) + def get_easter_monday(self, year): + "Return the date of the monday after easter" + sunday = self.get_easter_sunday(year) + return sunday + timedelta(days=1) - # it is sorted - self._holidays[year] = sorted(temp_calendar) - return self._holidays[year] + def get_ascension_thursday(self, year): + easter = self.get_easter_sunday(year) + return easter + timedelta(days=39) - def get_holiday_label(self, day): - """Return the label of the holiday, if the date is a holiday""" - day = cleaned_date(day) - return {day: label for day, label in self.holidays(day.year) - }.get(day) + def get_whit_monday(self, year): + easter = self.get_easter_sunday(year) + return easter + timedelta(days=50) - def get_observed_date(self, holiday): - """ - The date the holiday is observed for this calendar. If the holiday - occurs on a weekend, it may be observed on another day as indicated by - the observance_shift. + def get_whit_sunday(self, year): + easter = self.get_easter_sunday(year) + return easter + timedelta(days=49) - The holiday may also specify an 'observe_after' such that it is always - shifted after a preceding holiday. For example, Boxing day is always - observed after Christmas Day is observed. + def get_corpus_christi(self, year): + return self.get_easter_sunday(year) + timedelta(days=60) + + def shift_christmas_boxing_days(self, year): + """ When Christmas and/or Boxing Day falls on a weekend, it is rolled + forward to the next weekday. """ - # observance_shift may be overridden in the holiday itself - shift = getattr(holiday, 'observance_shift', self.observance_shift) - if callable(shift): - return shift(holiday, self) - shift = shift or {} - delta = rd.relativedelta(**shift) - should_shift = holiday.weekday() in self.get_weekend_days() - shifted = holiday + delta if should_shift else holiday - precedent = getattr(holiday, 'observe_after', None) - while precedent and shifted <= self.get_observed_date(precedent): - shifted += timedelta(days=1) - return shifted + christmas = date(year, 12, 25) + boxing_day = date(year, 12, 26) + boxing_day_label = "{} Shift".format(self.boxing_day_label) + results = [] + if christmas.weekday() in self.get_weekend_days(): + shift = self.find_following_working_day(christmas) + results.append((shift, "Christmas Shift")) + results.append((shift + timedelta(days=1), boxing_day_label)) + elif boxing_day.weekday() in self.get_weekend_days(): + shift = self.find_following_working_day(boxing_day) + results.append((shift, boxing_day_label)) + return results - def holidays_set(self, year=None): - "Return a quick date index (set)" - return set(self.holidays(year)) + def get_variable_days(self, year): # noqa + "Return the christian holidays list according to the mixin" + days = super().get_variable_days(year) + if self.include_epiphany: + days.append((date(year, 1, 6), "Epiphany")) + if self.include_clean_monday: + days.append((self.get_clean_monday(year), "Clean Monday")) + if self.include_annunciation: + days.append((date(year, 3, 25), "Annunciation")) + if self.include_fat_tuesday: + days.append( + (self.get_fat_tuesday(year), self.fat_tuesday_label) + ) + if self.include_ash_wednesday: + days.append( + (self.get_ash_wednesday(year), self.ash_wednesday_label) + ) + if self.include_palm_sunday: + days.append((self.get_palm_sunday(year), "Palm Sunday")) + if self.include_holy_thursday: + days.append((self.get_holy_thursday(year), "Holy Thursday")) + if self.include_good_friday: + days.append((self.get_good_friday(year), self.good_friday_label)) + if self.include_easter_saturday: + days.append((self.get_easter_saturday(year), "Easter Saturday")) + if self.include_easter_sunday: + days.append((self.get_easter_sunday(year), "Easter Sunday")) + if self.include_easter_monday: + days.append((self.get_easter_monday(year), "Easter Monday")) + if self.include_assumption: + days.append((date(year, 8, 15), "Assumption of Mary to Heaven")) + if self.include_all_saints: + days.append((date(year, 11, 1), "All Saints Day")) + if self.include_all_souls: + days.append((date(year, 11, 2), "All Souls Day")) + if self.include_immaculate_conception: + days.append((date(year, 12, 8), self.immaculate_conception_label)) + christmas = None + if self.include_christmas: + christmas = Holiday(date(year, 12, 25), "Christmas Day") + days.append(christmas) + if self.include_christmas_eve: + days.append((date(year, 12, 24), "Christmas Eve")) + if self.include_boxing_day: + boxing_day = Holiday( + date(year, 12, 26), + self.boxing_day_label, + indication="Day after Christmas", + observe_after=christmas + ) + days.append(boxing_day) + if self.include_ascension: + days.append(( + self.get_ascension_thursday(year), "Ascension Thursday")) + if self.include_whit_monday: + days.append((self.get_whit_monday(year), self.whit_monday_label)) + if self.include_whit_sunday: + days.append((self.get_whit_sunday(year), self.whit_sunday_label)) + if self.include_corpus_christi: + days.append((self.get_corpus_christi(year), "Corpus Christi")) + return days - def get_weekend_days(self): - """Return a list (or a tuple) of weekdays that are *not* working days. - e.g: return (SAT, SUN,) +class WesternMixin(ChristianMixin): + """ + General usage calendar for Western countries. - """ - if self.WEEKEND_DAYS: - return self.WEEKEND_DAYS - else: - raise NotImplementedError("Your Calendar class must provide " - "WEEKEND_DAYS or implement the " - "`get_weekend_days` method") + (chiefly Europe and Northern America) - def is_working_day(self, day, - extra_working_days=None, extra_holidays=None): - """Return True if it's a working day. - In addition to the regular holidays, you can add exceptions. + """ + EASTER_METHOD = easter.EASTER_WESTERN + WEEKEND_DAYS = (SAT, SUN) + include_new_years_day = False + shift_new_years_day = False - By providing ``extra_working_days``, you'll state that these dates - **are** working days. + def get_variable_days(self, year): + days = super().get_variable_days(year) + new_years = Holiday( + date(year, 1, 1), 'New year', indication='First day in January', + ) + if not self.shift_new_years_day: + new_years.observance_shift = None + days.append(new_years) + return days - By providing ``extra_holidays``, you'll state that these dates **are** - holidays, even if not in the regular calendar holidays (or weekends). - Please note that the ``extra_working_days`` list has priority over the - ``extra_holidays`` list. +class OrthodoxMixin(ChristianMixin): + EASTER_METHOD = easter.EASTER_ORTHODOX + WEEKEND_DAYS = (SAT, SUN) + include_orthodox_christmas = True + # This label should be de-duplicated if needed + orthodox_christmas_day_label = "Christmas" - """ - day = cleaned_date(day) - if extra_working_days: - extra_working_days = tuple(map(cleaned_date, extra_working_days)) - if extra_holidays: - extra_holidays = tuple(map(cleaned_date, extra_holidays)) + def get_fixed_holidays(self, year): + days = super().get_fixed_holidays(year) + if self.include_orthodox_christmas: + days.append( + (date(year, 1, 7), self.orthodox_christmas_day_label) + ) + return days - # Extra lists exceptions - if extra_working_days and day in extra_working_days: - return True - # Regular rules - if day.weekday() in self.get_weekend_days(): - return False - if extra_holidays and day in extra_holidays: - return False - return not self.is_observed_holiday(day) +class LunarMixin: + """ + Calendar ready to compute luncar calendar days + """ + @staticmethod + def lunar(year, month, day): + return LunarDate(year, month, day).toSolarDate() - def is_holiday(self, day, extra_holidays=None): - """Return True if it's an holiday. - In addition to the regular holidays, you can add exceptions. - By providing ``extra_holidays``, you'll state that these dates **are** - holidays, even if not in the regular calendar holidays (or weekends). +class ChineseNewYearMixin(LunarMixin): + """ + Calendar including toolsets to compute the Chinese New Year holidays. + """ + include_chinese_new_year_eve = False + chinese_new_year_eve_label = "Chinese New Year's eve" + # Chinese New Year will be included by default + include_chinese_new_year = True + chinese_new_year_label = 'Chinese New Year' + # Some countries include the 2nd lunar day as a holiday + include_chinese_second_day = False + chinese_second_day_label = "Chinese New Year (2nd day)" + include_chinese_third_day = False + chinese_third_day_label = "Chinese New Year (3rd day)" + shift_sunday_holidays = False + # Some calendars roll a starting Sunday CNY to Sat + shift_start_cny_sunday = False + def get_chinese_new_year(self, year): """ - day = cleaned_date(day) + Compute Chinese New Year days. To return a list of holidays. - if extra_holidays: - extra_holidays = tuple(map(cleaned_date, extra_holidays)) + By default, it'll at least return the Chinese New Year holidays chosen + using the following options: - if extra_holidays and day in extra_holidays: - return True + * ``include_chinese_new_year_eve`` + * ``include_chinese_new_year`` (on by default) + * ``include_chinese_second_day`` - return day in self.holidays_set(day.year) + If the ``shift_sunday_holidays`` option is on, the rules are the + following. - def is_observed_holiday(self, day): - """Return True if it's an observed holiday. + * If the CNY1 falls on MON-FRI, there's not shift. + * If the CNY1 falls on SAT, the CNY2 is shifted to the Monday after. + * If the CNY1 falls on SUN, the CNY1 is shifted to the Monday after, + and CNY2 is shifted to the Tuesday after. """ - observed = set(map(self.get_observed_date, self.holidays(day.year))) - return day in observed - - def add_working_days(self, day, delta, - extra_working_days=None, extra_holidays=None, - keep_datetime=False): - """Add `delta` working days to the date. + days = [] - You can provide either a date or a datetime to this function that will - output a ``date`` result. You can alter this behaviour using the - ``keep_datetime`` option set to ``True``. + lunar_first_day = ChineseNewYearMixin.lunar(year, 1, 1) + # Chinese new year's eve + if self.include_chinese_new_year_eve: + days.append(( + lunar_first_day - timedelta(days=1), + self.chinese_new_year_eve_label + )) + # Chinese new year (is included by default) + if self.include_chinese_new_year: + days.append((lunar_first_day, self.chinese_new_year_label)) - the ``delta`` parameter might be positive or negative. If it's - negative, you may want to use the ``sub_working_days()`` method with - a positive ``delta`` argument. + if self.include_chinese_second_day: + lunar_second_day = lunar_first_day + timedelta(days=1) + days.append(( + lunar_second_day, + self.chinese_second_day_label + )) + if self.include_chinese_third_day: + lunar_third_day = lunar_first_day + timedelta(days=2) + days.append(( + lunar_third_day, + self.chinese_third_day_label + )) - By providing ``extra_working_days``, you'll state that these dates - **are** working days. + if self.shift_sunday_holidays: + if lunar_first_day.weekday() == SUN: + if self.shift_start_cny_sunday: + days.append( + (lunar_first_day - timedelta(days=1), + "Chinese Lunar New Year shift"), + ) + else: + if self.include_chinese_third_day: + shift_day = lunar_third_day + else: + shift_day = lunar_second_day + days.append( + (shift_day + timedelta(days=1), + "Chinese Lunar New Year shift"), + ) + if (lunar_second_day.weekday() == SUN + and self.include_chinese_third_day): + days.append( + (lunar_third_day + timedelta(days=1), + "Chinese Lunar New Year shift"), + ) + return days - By providing ``extra_holidays``, you'll state that these dates **are** - holidays, even if not in the regular calendar holidays (or weekends). + def get_variable_days(self, year): + days = super().get_variable_days(year) + days.extend(self.get_chinese_new_year(year)) + return days - Please note that the ``extra_working_days`` list has priority over the - ``extra_holidays`` list. + @staticmethod + def observance_shift_for_sunday(holiday, calendar): """ - day = cleaned_date(day, keep_datetime) - - if extra_working_days: - extra_working_days = tuple(map(cleaned_date, extra_working_days)) - - if extra_holidays: - extra_holidays = tuple(map(cleaned_date, extra_holidays)) + Taking an existing holiday, return a 'shifted' day to skip on from SUN. + """ + return holiday + timedelta(days=1) - days = 0 - temp_day = day - day_added = 1 if delta >= 0 else -1 - delta = abs(delta) - while days < delta: - temp_day = temp_day + timedelta(days=day_added) - if self.is_working_day(temp_day, - extra_working_days=extra_working_days, - extra_holidays=extra_holidays): - days += 1 - return temp_day + def get_shifted_holidays(self, dates): + """ + Taking a list of existing holidays, yield a list of 'shifted' days if + the holiday falls on SUN. + """ + for holiday, label in dates: + if isinstance(holiday, date): + holiday = Holiday(holiday, label) + if holiday.weekday() == SUN: + holiday.observance_shift = self.observance_shift_for_sunday + yield holiday - def sub_working_days(self, day, delta, - extra_working_days=None, extra_holidays=None, - keep_datetime=False): + def get_calendar_holidays(self, year): """ - Substract `delta` working days to the date. + Take into account the eventual shift to the next MON if any holiday + falls on SUN. + """ + # Unshifted days are here: + days = super().get_calendar_holidays(year) + if self.shift_sunday_holidays: + days = self.get_shifted_holidays(days) + return days - This method is a shortcut / helper. Users may want to use either:: - cal.add_working_days(my_date, -7) - cal.sub_working_days(my_date, 7) +class CalverterMixin: + conversion_method = None + ISLAMIC_HOLIDAYS = () - The other parameters are to be used exactly as in the - ``add_working_days`` method. + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.calverter = Calverter() + if self.conversion_method is None: + raise NotImplementedError - A negative ``delta`` argument will be converted into its absolute - value. Hence, the two following calls are equivalent:: + def converted(self, year): + conversion_method = getattr( + self.calverter, 'jd_to_%s' % self.conversion_method) + current = date(year, 1, 1) + days = [] + while current.year == year: + julian_day = self.calverter.gregorian_to_jd( + current.year, + current.month, + current.day) + days.append(conversion_method(julian_day)) + current = current + timedelta(days=1) + return days - cal.sub_working_days(my_date, -7) - cal.sub_working_days(my_date, 7) + def calverted_years(self, year): + converted = self.converted(year) + generator = (y for y, m, d in converted) + return sorted(list(set(generator))) - As in ``add_working_days()`` you can set the parameter - ``keep_datetime`` to ``True`` to make sure that if your ``day`` - argument is a ``datetime``, the returned date will also be a - ``datetime`` object. + def get_islamic_holidays(self): + return self.ISLAMIC_HOLIDAYS + def get_delta_islamic_holidays(self, year): """ - delta = abs(delta) - return self.add_working_days( - day, -delta, - extra_working_days, extra_holidays, keep_datetime=keep_datetime) - - def find_following_working_day(self, day): - """Looks for the following working day, if not already a working day. + Return the delta to add/substract according to the year or customs. - **WARNING**: this function doesn't take into account the calendar - holidays, only the days of the week and the weekend days parameters. + By default, to return None or timedelta(days=0) """ - day = cleaned_date(day) - - while day.weekday() in self.get_weekend_days(): - day = day + timedelta(days=1) - return day + return None - @staticmethod - def get_nth_weekday_in_month(year, month, weekday, n=1, start=None): - """Get the nth weekday in a given month. e.g: + def get_variable_days(self, year): + warnings.warn('Please take note that, due to arbitrary decisions, ' + 'this Islamic calendar computation may be wrong.') + days = super().get_variable_days(year) + years = self.calverted_years(year) + conversion_method = getattr( + self.calverter, '%s_to_jd' % self.conversion_method) + for month, day, label in self.get_islamic_holidays(): + for y in years: + jd = conversion_method(y, month, day) + g_year, g_month, g_day = self.calverter.jd_to_gregorian(jd) + holiday = date(g_year, g_month, g_day) - >>> # the 1st monday in Jan 2013 - >>> Calendar.get_nth_weekday_in_month(2013, 1, MON) - datetime.date(2013, 1, 7) - >>> # The 2nd monday in Jan 2013 - >>> Calendar.get_nth_weekday_in_month(2013, 1, MON, 2) - datetime.date(2013, 1, 14) - """ - # If start is `None` or Falsy, no need to check and clean - if start: - start = cleaned_date(start) + # Only add a delta if necessary + delta = self.get_delta_islamic_holidays(year) + if delta: + holiday += delta - day = date(year, month, 1) - if start: - day = start - counter = 0 - while True: - if day.month != month: - # Don't forget to break if "n" is too big - return None - if day.weekday() == weekday: - counter += 1 - if counter == n: - break - day = day + timedelta(days=1) - return day + if holiday.year == year: + days.append((holiday, label)) + return days - @staticmethod - def get_last_weekday_in_month(year, month, weekday): - """Get the last weekday in a given month. e.g: - >>> # the last monday in Jan 2013 - >>> Calendar.get_last_weekday_in_month(2013, 1, MON) - datetime.date(2013, 1, 28) - """ - day = date(year, month, monthrange(year, month)[1]) - while True: - if day.weekday() == weekday: - break - day = day - timedelta(days=1) - return day +class IslamicMixin(SeriesShiftMixin, CalverterMixin): - @staticmethod - def get_first_weekday_after(day, weekday): - """Get the first weekday after a given day. If the day is the same - weekday, the same day will be returned. + WEEKEND_DAYS = (FRI, SAT) - >>> # the first monday after Apr 1 2015 - >>> Calendar.get_first_weekday_after(date(2015, 4, 1), MON) - datetime.date(2015, 4, 6) + conversion_method = 'islamic' + include_prophet_birthday = False + include_day_after_prophet_birthday = False + include_start_ramadan = False + include_eid_al_fitr = False + length_eid_al_fitr = 1 + eid_al_fitr_label = "Eid al-Fitr" + include_eid_al_adha = False + length_eid_al_adha = 1 + include_day_of_sacrifice = False + day_of_sacrifice_label = "Eid al-Adha" + include_islamic_new_year = False + include_laylat_al_qadr = False + include_nuzul_al_quran = False - >>> # the first tuesday after Apr 14 2015 - >>> Calendar.get_first_weekday_after(date(2015, 4, 14), TUE) - datetime.date(2015, 4, 14) - """ - day_delta = (weekday - day.weekday()) % 7 - day = day + timedelta(days=day_delta) - return day + def get_islamic_holidays(self): # noqa: C901 + """Return a list of Islamic (month, day, label) for islamic holidays. + Please take note that these dates must be expressed using the Islamic + Calendar""" + days = list(super().get_islamic_holidays()) - def get_working_days_delta(self, start, end, include_start=False): - """ - Return the number of working day between two given dates. - The order of the dates provided doesn't matter. + if self.include_islamic_new_year: + days.append((1, 1, "Islamic New Year")) + if self.include_prophet_birthday: + days.append((3, 12, "Prophet's Birthday")) + if self.include_day_after_prophet_birthday: + days.append((3, 13, "Day after Prophet's Birthday")) + if self.include_start_ramadan: + days.append((9, 1, "Start of ramadan")) + if self.include_nuzul_al_quran: + days.append((9, 17, "Nuzul Al-Qur'an")) + if self.include_eid_al_fitr: + for x in range(self.length_eid_al_fitr): + days.append((10, x + 1, self.eid_al_fitr_label)) + if self.include_eid_al_adha: + for x in range(self.length_eid_al_adha): + days.append((12, x + 10, "Eid al-Adha")) + if self.include_day_of_sacrifice: + days.append((12, 10, self.day_of_sacrifice_label)) + if self.include_laylat_al_qadr: + warnings.warn("The Islamic holiday named Laylat al-Qadr is decided" + " by the religious authorities. It is not possible" + " to compute it. You'll have to add it manually.") + return tuple(days) - In the following example, there are 5 days, because of the week-end: + def get_calendar_holidays(self, year): + self.series_requiring_shifts = [self.eid_al_fitr_label, + self.day_of_sacrifice_label] + return super().get_calendar_holidays(year) - >>> cal = WesternCalendar() # does not include easter monday - >>> day1 = date(2018, 3, 29) - >>> day2 = date(2018, 4, 5) - >>> cal.get_working_days_delta(day1, day2) - 5 - In France, April 1st 2018 is a holiday because it's Easter monday: +class JalaliMixin(CalverterMixin): + conversion_method = 'jalali' - >>> from calendra.europe import France - >>> cal = France() - >>> cal.get_working_days_delta(day1, day2) - 4 - This method should even work if your ``start`` and ``end`` arguments - are datetimes. +class CoreCalendar: - By default, if the day after you start is not a working day, - the count will start at 0. If include_start is set to true, - this day will be taken into account. + FIXED_HOLIDAYS = () + WEEKEND_DAYS = () + observance_shift = dict(weekday=rd.MO(1)) + """ + The shift for the observance of a holiday defined as keyword parameters to + a rd.relativedelta instance. + By default, holidays are shifted to the Monday following the weekend. + """ - Example: + def __init__(self): + self._holidays = {} - >>> from dateutil.parser import parse - >>> cal = France() - >>> day_1 = parse('09/05/2018 00:01', dayfirst=True) - >>> day_2 = parse('10/05/2018 19:01', dayfirst=True) # holiday in france - >>> cal.get_working_days_delta(day_1, day_2) - 0 + @classproperty + def name(cls): + class_name = cls.__name__ + if cls.__doc__: + doc = cls.__doc__.split('\n') + doc = map(lambda s: s.strip(), doc) + return next(s for s in doc if s) + return class_name - >>> cal.get_working_days_delta(day_1, day_2, include_start=True) - 1 + def get_fixed_holidays(self, year): + """Return the fixed days according to the FIXED_HOLIDAYS class property """ - start = cleaned_date(start) - end = cleaned_date(end) - - if start == end: - return 0 - - if start > end: - start, end = end, start + fixed_holidays = map( + Holiday._from_fixed_definition, + self.FIXED_HOLIDAYS, + ) + return [day.replace(year=year) for day in fixed_holidays] - # Starting count here - count = 1 if include_start and self.is_working_day(start) else 0 - while start < end: - start += timedelta(days=1) - if self.is_working_day(start): - count += 1 - return count + def get_variable_days(self, year): + return [] - def _get_ical_period(self, period=None): - """ - Return a usable period for iCal export + def get_calendar_holidays(self, year): + """Get calendar holidays. + If you want to override this, please make sure that it **must** return + a list of tuples (date, holiday_name).""" + return self.get_fixed_holidays(year) + self.get_variable_days(year) - Default period is [2000, 2030] - """ - # Default value. - if not period: - period = [2000, 2030] + def holidays(self, year=None): + """Computes holidays (non-working days) for a given year. + Return a 2-item tuple, composed of the date and a label.""" + if not year: + year = date.today().year - # Make sure it's a usable iterable - if type(period) not in (list, tuple): - raise ICalExportRangeError( - "Incorrect Range type. Must be list or tuple.") + if year in self._holidays: + return self._holidays[year] - # Taking the extremes - period = [min(period), max(period)] + # Here we process the holiday specific calendar + days = self.get_calendar_holidays(year) + days = map(Holiday._from_resolved_definition, days) + temp_calendar = tuple(days) - # check for internal types - check_types = map(type, period) - check_types = map(lambda x: x != int, check_types) - if any(check_types): - raise ICalExportRangeError( - "Incorrect Range boundaries. Must be int.") + # it is sorted + self._holidays[year] = sorted(temp_calendar) + return self._holidays[year] - return period + def get_holiday_label(self, day): + """Return the label of the holiday, if the date is a holiday""" + day = cleaned_date(day) + return {day: label for day, label in self.holidays(day.year) + }.get(day) - def _get_ical_target_path(self, target_path): + def get_observed_date(self, holiday): """ - Return target path for iCal export. + The date the holiday is observed for this calendar. If the holiday + occurs on a weekend, it may be observed on another day as indicated by + the observance_shift. - Note - ---- - If `target_path` does not have one of the extensions `.ical`, `.ics`, - `.ifb`, or `.icalendar`, the extension `.ics` is appended to the path. - Returns - ------- - None. - Examples - -------- - >>> from calendra.europe import Austria - >>> cal = Austria() - >>> cal._get_ical_target_path('austria') - 'austria.ics' + The holiday may also specify an 'observe_after' such that it is always + shifted after a preceding holiday. For example, Boxing day is always + observed after Christmas Day is observed. """ + # observance_shift may be overridden in the holiday itself + shift = getattr(holiday, 'observance_shift', self.observance_shift) + if callable(shift): + return shift(holiday, self) + shift = shift or {} + delta = rd.relativedelta(**shift) + should_shift = holiday.weekday() in self.get_weekend_days() + shifted = holiday + delta if should_shift else holiday + precedent = getattr(holiday, 'observe_after', None) + while precedent and shifted <= self.get_observed_date(precedent): + shifted += timedelta(days=1) + return shifted - if not target_path: - raise ICalExportTargetPathError( - "Incorrect target path. It must not be empty") - - if isdir(target_path): - raise ICalExportTargetPathError( - "Incorrect target path. It must not be a directory" - ) - - ical_extensions = ['.ical', '.ics', '.ifb', '.icalendar'] - if os.path.splitext(target_path)[1] not in ical_extensions: - target_path += '.ics' - return target_path - - def export_to_ical(self, period=[2000, 2030], target_path=None): - """ - Export the calendar to iCal (RFC 5545) format. + def holidays_set(self, year=None): + "Return a quick date index (set)" + return set(self.holidays(year)) - Parameters - ---------- - period: [int, int] - start and end year (inclusive) of calendar - Default is [2000, 2030] + def get_weekend_days(self): + """Return a list (or a tuple) of weekdays that are *not* working days. - target_path: str - the name or path of the exported file. If this argument is missing, - the function will return the ical content. + e.g: return (SAT, SUN,) """ - first_year, last_year = self._get_ical_period(period) - if target_path: - # Generate filename path before calculate the holidays - target_path = self._get_ical_target_path(target_path) - - # fetch holidays - holidays = [] - for year in range(first_year, last_year + 1): - holidays.extend(self.holidays(year)) + if self.WEEKEND_DAYS: + return self.WEEKEND_DAYS + else: + raise NotImplementedError("Your Calendar class must provide " + "WEEKEND_DAYS or implement the " + "`get_weekend_days` method") - # initialize icalendar - ics = [ - 'BEGIN:VCALENDAR', - 'VERSION:2.0', # current RFC5545 version - f'PRODID:-//workalendar//ical {__version__}//EN' - ] - common_timestamp = datetime.utcnow().strftime('%Y%m%dT%H%M%SZ') - dtstamp = 'DTSTAMP;VALUE=DATE-TIME:%s' % common_timestamp + def is_working_day(self, day, + extra_working_days=None, extra_holidays=None): + """Return True if it's a working day. + In addition to the regular holidays, you can add exceptions. - # add an event for each holiday - for holiday in holidays: - date_ = self.get_observed_date(holiday) - ics.extend([ - 'BEGIN:VEVENT', - 'SUMMARY:%s' % holiday.name, - 'DTSTART;VALUE=DATE:%s' % date_.strftime('%Y%m%d'), - dtstamp, - f'UID:{date_}{holiday.name}@peopledoc.github.io/workalendar', - 'END:VEVENT', - ]) + By providing ``extra_working_days``, you'll state that these dates + **are** working days. - # add footer - ics.append('END:VCALENDAR\n') # last line with a trailing \n + By providing ``extra_holidays``, you'll state that these dates **are** + holidays, even if not in the regular calendar holidays (or weekends). - # Transform this list into text lines - ics = "\n".join(ics) + Please note that the ``extra_working_days`` list has priority over the + ``extra_holidays`` list. - if target_path: - # save iCal file - with open(target_path, 'w+') as export_file: - export_file.write(ics) - return + """ + day = cleaned_date(day) + if extra_working_days: + extra_working_days = tuple(map(cleaned_date, extra_working_days)) + if extra_holidays: + extra_holidays = tuple(map(cleaned_date, extra_holidays)) - return ics + # Extra lists exceptions + if extra_working_days and day in extra_working_days: + return True + # Regular rules + if day.weekday() in self.get_weekend_days(): + return False + if extra_holidays and day in extra_holidays: + return False + return not self.is_observed_holiday(day) -class Calendar(CoreCalendar): - """ - The cornerstone of Earth calendars. + def is_holiday(self, day, extra_holidays=None): + """Return True if it's an holiday. + In addition to the regular holidays, you can add exceptions. - Take care of the New Years Day, which is almost a worldwide holiday. - """ - include_new_years_day = True - shift_new_years_day = False - include_labour_day = False - labour_day_label = "Labour Day" + By providing ``extra_holidays``, you'll state that these dates **are** + holidays, even if not in the regular calendar holidays (or weekends). - def __init__(self, **kwargs): - super().__init__() + """ + day = cleaned_date(day) - def get_fixed_holidays(self, year): - days = super().get_fixed_holidays(year) - if self.include_new_years_day: - days.insert( - 0, (date(year, 1, 1), "New year") - ) + if extra_holidays: + extra_holidays = tuple(map(cleaned_date, extra_holidays)) - if self.include_labour_day: - days.append( - (date(year, 5, 1), self.labour_day_label) - ) - return days + if extra_holidays and day in extra_holidays: + return True - def get_variable_days(self, year): - days = super().get_variable_days(year) - new_year = date(year, 1, 1) - if self.include_new_years_day and self.shift_new_years_day: - if new_year.weekday() in self.get_weekend_days(): - days.append(( - self.find_following_working_day(new_year), - "New Year shift")) - return days + return day in self.holidays_set(day.year) + def is_observed_holiday(self, day): + """Return True if it's an observed holiday. + """ + observed = set(map(self.get_observed_date, self.holidays(day.year))) + return day in observed -class ChristianMixin: - EASTER_METHOD = None # to be assigned in the inherited mixin - include_epiphany = False - include_clean_monday = False - include_annunciation = False - include_fat_tuesday = False - # Fat tuesday forced to `None` to make sure this value is always set - # We've seen that there was a wide variety of labels. - fat_tuesday_label = None - include_ash_wednesday = False - ash_wednesday_label = "Ash Wednesday" - include_palm_sunday = False - include_holy_thursday = False - include_good_friday = False - good_friday_label = "Good Friday" - include_easter_monday = False - include_easter_saturday = False - include_easter_sunday = False - include_all_saints = False - include_immaculate_conception = False - immaculate_conception_label = "Immaculate Conception" - include_christmas = True - include_christmas_eve = False - include_ascension = False - include_assumption = False - include_whit_sunday = False - whit_sunday_label = 'Whit Sunday' - include_whit_monday = False - whit_monday_label = 'Whit Monday' - include_corpus_christi = False - include_boxing_day = False - boxing_day_label = "Boxing Day" - include_all_souls = False + def add_working_days(self, day, delta, + extra_working_days=None, extra_holidays=None, + keep_datetime=False): + """Add `delta` working days to the date. - def get_fat_tuesday(self, year): - if not self.fat_tuesday_label: - raise CalendarError( - "Improperly configured: please provide a " - "`fat_tuesday_label` value") - sunday = self.get_easter_sunday(year) - return sunday - timedelta(days=47) + You can provide either a date or a datetime to this function that will + output a ``date`` result. You can alter this behaviour using the + ``keep_datetime`` option set to ``True``. - def get_ash_wednesday(self, year): - sunday = self.get_easter_sunday(year) - return sunday - timedelta(days=46) + the ``delta`` parameter might be positive or negative. If it's + negative, you may want to use the ``sub_working_days()`` method with + a positive ``delta`` argument. - def get_palm_sunday(self, year): - sunday = self.get_easter_sunday(year) - return sunday - timedelta(days=7) + By providing ``extra_working_days``, you'll state that these dates + **are** working days. - def get_holy_thursday(self, year): - "Return the date of the last thursday before easter" - sunday = self.get_easter_sunday(year) - return sunday - timedelta(days=3) + By providing ``extra_holidays``, you'll state that these dates **are** + holidays, even if not in the regular calendar holidays (or weekends). - def get_good_friday(self, year): - "Return the date of the last friday before easter" - sunday = self.get_easter_sunday(year) - return sunday - timedelta(days=2) + Please note that the ``extra_working_days`` list has priority over the + ``extra_holidays`` list. + """ + day = cleaned_date(day, keep_datetime) - def get_clean_monday(self, year): - "Return the clean monday date" - sunday = self.get_easter_sunday(year) - return sunday - timedelta(days=48) + if extra_working_days: + extra_working_days = tuple(map(cleaned_date, extra_working_days)) - def get_easter_saturday(self, year): - "Return the Easter Saturday date" - sunday = self.get_easter_sunday(year) - return sunday - timedelta(days=1) + if extra_holidays: + extra_holidays = tuple(map(cleaned_date, extra_holidays)) - def get_easter_sunday(self, year): - "Return the date of the easter (sunday) -- following the easter method" - return easter.easter(year, self.EASTER_METHOD) + days = 0 + temp_day = day + day_added = 1 if delta >= 0 else -1 + delta = abs(delta) + while days < delta: + temp_day = temp_day + timedelta(days=day_added) + if self.is_working_day(temp_day, + extra_working_days=extra_working_days, + extra_holidays=extra_holidays): + days += 1 + return temp_day - def get_easter_monday(self, year): - "Return the date of the monday after easter" - sunday = self.get_easter_sunday(year) - return sunday + timedelta(days=1) + def sub_working_days(self, day, delta, + extra_working_days=None, extra_holidays=None, + keep_datetime=False): + """ + Substract `delta` working days to the date. - def get_ascension_thursday(self, year): - easter = self.get_easter_sunday(year) - return easter + timedelta(days=39) + This method is a shortcut / helper. Users may want to use either:: - def get_whit_monday(self, year): - easter = self.get_easter_sunday(year) - return easter + timedelta(days=50) + cal.add_working_days(my_date, -7) + cal.sub_working_days(my_date, 7) - def get_whit_sunday(self, year): - easter = self.get_easter_sunday(year) - return easter + timedelta(days=49) + The other parameters are to be used exactly as in the + ``add_working_days`` method. - def get_corpus_christi(self, year): - return self.get_easter_sunday(year) + timedelta(days=60) + A negative ``delta`` argument will be converted into its absolute + value. Hence, the two following calls are equivalent:: - def shift_christmas_boxing_days(self, year): - """ When Christmas and/or Boxing Day falls on a weekend, it is rolled - forward to the next weekday. - """ - christmas = date(year, 12, 25) - boxing_day = date(year, 12, 26) - boxing_day_label = "{} Shift".format(self.boxing_day_label) - results = [] - if christmas.weekday() in self.get_weekend_days(): - shift = self.find_following_working_day(christmas) - results.append((shift, "Christmas Shift")) - results.append((shift + timedelta(days=1), boxing_day_label)) - elif boxing_day.weekday() in self.get_weekend_days(): - shift = self.find_following_working_day(boxing_day) - results.append((shift, boxing_day_label)) - return results + cal.sub_working_days(my_date, -7) + cal.sub_working_days(my_date, 7) - def get_variable_days(self, year): # noqa - "Return the christian holidays list according to the mixin" - days = super().get_variable_days(year) - if self.include_epiphany: - days.append((date(year, 1, 6), "Epiphany")) - if self.include_clean_monday: - days.append((self.get_clean_monday(year), "Clean Monday")) - if self.include_annunciation: - days.append((date(year, 3, 25), "Annunciation")) - if self.include_fat_tuesday: - days.append( - (self.get_fat_tuesday(year), self.fat_tuesday_label) - ) - if self.include_ash_wednesday: - days.append( - (self.get_ash_wednesday(year), self.ash_wednesday_label) - ) - if self.include_palm_sunday: - days.append((self.get_palm_sunday(year), "Palm Sunday")) - if self.include_holy_thursday: - days.append((self.get_holy_thursday(year), "Holy Thursday")) - if self.include_good_friday: - days.append((self.get_good_friday(year), self.good_friday_label)) - if self.include_easter_saturday: - days.append((self.get_easter_saturday(year), "Easter Saturday")) - if self.include_easter_sunday: - days.append((self.get_easter_sunday(year), "Easter Sunday")) - if self.include_easter_monday: - days.append((self.get_easter_monday(year), "Easter Monday")) - if self.include_assumption: - days.append((date(year, 8, 15), "Assumption of Mary to Heaven")) - if self.include_all_saints: - days.append((date(year, 11, 1), "All Saints Day")) - if self.include_all_souls: - days.append((date(year, 11, 2), "All Souls Day")) - if self.include_immaculate_conception: - days.append((date(year, 12, 8), self.immaculate_conception_label)) - christmas = None - if self.include_christmas: - christmas = Holiday(date(year, 12, 25), "Christmas Day") - days.append(christmas) - if self.include_christmas_eve: - days.append((date(year, 12, 24), "Christmas Eve")) - if self.include_boxing_day: - boxing_day = Holiday( - date(year, 12, 26), - self.boxing_day_label, - indication="Day after Christmas", - observe_after=christmas - ) - days.append(boxing_day) - if self.include_ascension: - days.append(( - self.get_ascension_thursday(year), "Ascension Thursday")) - if self.include_whit_monday: - days.append((self.get_whit_monday(year), self.whit_monday_label)) - if self.include_whit_sunday: - days.append((self.get_whit_sunday(year), self.whit_sunday_label)) - if self.include_corpus_christi: - days.append((self.get_corpus_christi(year), "Corpus Christi")) - return days + As in ``add_working_days()`` you can set the parameter + ``keep_datetime`` to ``True`` to make sure that if your ``day`` + argument is a ``datetime``, the returned date will also be a + ``datetime`` object. + """ + delta = abs(delta) + return self.add_working_days( + day, -delta, + extra_working_days, extra_holidays, keep_datetime=keep_datetime) -class WesternMixin(ChristianMixin): - """ - General usage calendar for Western countries. + def find_following_working_day(self, day): + """Looks for the following working day, if not already a working day. - (chiefly Europe and Northern America) + **WARNING**: this function doesn't take into account the calendar + holidays, only the days of the week and the weekend days parameters. + """ + day = cleaned_date(day) - """ - EASTER_METHOD = easter.EASTER_WESTERN - WEEKEND_DAYS = (SAT, SUN) - include_new_years_day = False - shift_new_years_day = False + while day.weekday() in self.get_weekend_days(): + day = day + timedelta(days=1) + return day - def get_variable_days(self, year): - days = super().get_variable_days(year) - new_years = Holiday( - date(year, 1, 1), 'New year', indication='First day in January', - ) - if not self.shift_new_years_day: - new_years.observance_shift = None - days.append(new_years) - return days + @staticmethod + def get_nth_weekday_in_month(year, month, weekday, n=1, start=None): + """Get the nth weekday in a given month. e.g: + >>> # the 1st monday in Jan 2013 + >>> Calendar.get_nth_weekday_in_month(2013, 1, MON) + datetime.date(2013, 1, 7) + >>> # The 2nd monday in Jan 2013 + >>> Calendar.get_nth_weekday_in_month(2013, 1, MON, 2) + datetime.date(2013, 1, 14) + """ + # If start is `None` or Falsy, no need to check and clean + if start: + start = cleaned_date(start) -class WesternCalendar(WesternMixin, Calendar): - pass + day = date(year, month, 1) + if start: + day = start + counter = 0 + while True: + if day.month != month: + # Don't forget to break if "n" is too big + return None + if day.weekday() == weekday: + counter += 1 + if counter == n: + break + day = day + timedelta(days=1) + return day + @staticmethod + def get_last_weekday_in_month(year, month, weekday): + """Get the last weekday in a given month. e.g: -class OrthodoxMixin(ChristianMixin): - EASTER_METHOD = easter.EASTER_ORTHODOX - WEEKEND_DAYS = (SAT, SUN) - include_orthodox_christmas = True - # This label should be de-duplicated if needed - orthodox_christmas_day_label = "Christmas" + >>> # the last monday in Jan 2013 + >>> Calendar.get_last_weekday_in_month(2013, 1, MON) + datetime.date(2013, 1, 28) + """ + day = date(year, month, monthrange(year, month)[1]) + while True: + if day.weekday() == weekday: + break + day = day - timedelta(days=1) + return day - def get_fixed_holidays(self, year): - days = super().get_fixed_holidays(year) - if self.include_orthodox_christmas: - days.append( - (date(year, 1, 7), self.orthodox_christmas_day_label) - ) - return days + @staticmethod + def get_first_weekday_after(day, weekday): + """Get the first weekday after a given day. If the day is the same + weekday, the same day will be returned. + + >>> # the first monday after Apr 1 2015 + >>> Calendar.get_first_weekday_after(date(2015, 4, 1), MON) + datetime.date(2015, 4, 6) + >>> # the first tuesday after Apr 14 2015 + >>> Calendar.get_first_weekday_after(date(2015, 4, 14), TUE) + datetime.date(2015, 4, 14) + """ + day_delta = (weekday - day.weekday()) % 7 + day = day + timedelta(days=day_delta) + return day -class OrthodoxCalendar(OrthodoxMixin, Calendar): - pass + def get_working_days_delta(self, start, end, include_start=False): + """ + Return the number of working day between two given dates. + The order of the dates provided doesn't matter. + In the following example, there are 5 days, because of the week-end: -class LunarMixin: - """ - Calendar ready to compute luncar calendar days - """ - @staticmethod - def lunar(year, month, day): - return LunarDate(year, month, day).toSolarDate() + >>> cal = WesternCalendar() # does not include easter monday + >>> day1 = date(2018, 3, 29) + >>> day2 = date(2018, 4, 5) + >>> cal.get_working_days_delta(day1, day2) + 5 + In France, April 1st 2018 is a holiday because it's Easter monday: -class LunarCalendar(LunarMixin, Calendar): - pass + >>> from calendra.europe import France + >>> cal = France() + >>> cal.get_working_days_delta(day1, day2) + 4 + This method should even work if your ``start`` and ``end`` arguments + are datetimes. -class ChineseNewYearMixin(LunarMixin): - """ - Calendar including toolsets to compute the Chinese New Year holidays. - """ - include_chinese_new_year_eve = False - chinese_new_year_eve_label = "Chinese New Year's eve" - # Chinese New Year will be included by default - include_chinese_new_year = True - chinese_new_year_label = 'Chinese New Year' - # Some countries include the 2nd lunar day as a holiday - include_chinese_second_day = False - chinese_second_day_label = "Chinese New Year (2nd day)" - include_chinese_third_day = False - chinese_third_day_label = "Chinese New Year (3rd day)" - shift_sunday_holidays = False - # Some calendars roll a starting Sunday CNY to Sat - shift_start_cny_sunday = False + By default, if the day after you start is not a working day, + the count will start at 0. If include_start is set to true, + this day will be taken into account. - def get_chinese_new_year(self, year): + Example: + + >>> from dateutil.parser import parse + >>> cal = France() + >>> day_1 = parse('09/05/2018 00:01', dayfirst=True) + >>> day_2 = parse('10/05/2018 19:01', dayfirst=True) # holiday in france + >>> cal.get_working_days_delta(day_1, day_2) + 0 + + >>> cal.get_working_days_delta(day_1, day_2, include_start=True) + 1 """ - Compute Chinese New Year days. To return a list of holidays. + start = cleaned_date(start) + end = cleaned_date(end) - By default, it'll at least return the Chinese New Year holidays chosen - using the following options: + if start == end: + return 0 - * ``include_chinese_new_year_eve`` - * ``include_chinese_new_year`` (on by default) - * ``include_chinese_second_day`` + if start > end: + start, end = end, start - If the ``shift_sunday_holidays`` option is on, the rules are the - following. + # Starting count here + count = 1 if include_start and self.is_working_day(start) else 0 + while start < end: + start += timedelta(days=1) + if self.is_working_day(start): + count += 1 + return count - * If the CNY1 falls on MON-FRI, there's not shift. - * If the CNY1 falls on SAT, the CNY2 is shifted to the Monday after. - * If the CNY1 falls on SUN, the CNY1 is shifted to the Monday after, - and CNY2 is shifted to the Tuesday after. + def _get_ical_period(self, period=None): """ - days = [] + Return a usable period for iCal export - lunar_first_day = ChineseNewYearMixin.lunar(year, 1, 1) - # Chinese new year's eve - if self.include_chinese_new_year_eve: - days.append(( - lunar_first_day - timedelta(days=1), - self.chinese_new_year_eve_label - )) - # Chinese new year (is included by default) - if self.include_chinese_new_year: - days.append((lunar_first_day, self.chinese_new_year_label)) + Default period is [2000, 2030] + """ + # Default value. + if not period: + period = [2000, 2030] - if self.include_chinese_second_day: - lunar_second_day = lunar_first_day + timedelta(days=1) - days.append(( - lunar_second_day, - self.chinese_second_day_label - )) - if self.include_chinese_third_day: - lunar_third_day = lunar_first_day + timedelta(days=2) - days.append(( - lunar_third_day, - self.chinese_third_day_label - )) + # Make sure it's a usable iterable + if type(period) not in (list, tuple): + raise ICalExportRangeError( + "Incorrect Range type. Must be list or tuple.") - if self.shift_sunday_holidays: - if lunar_first_day.weekday() == SUN: - if self.shift_start_cny_sunday: - days.append( - (lunar_first_day - timedelta(days=1), - "Chinese Lunar New Year shift"), - ) - else: - if self.include_chinese_third_day: - shift_day = lunar_third_day - else: - shift_day = lunar_second_day - days.append( - (shift_day + timedelta(days=1), - "Chinese Lunar New Year shift"), - ) - if (lunar_second_day.weekday() == SUN - and self.include_chinese_third_day): - days.append( - (lunar_third_day + timedelta(days=1), - "Chinese Lunar New Year shift"), - ) - return days + # Taking the extremes + period = [min(period), max(period)] + + # check for internal types + check_types = map(type, period) + check_types = map(lambda x: x != int, check_types) + if any(check_types): + raise ICalExportRangeError( + "Incorrect Range boundaries. Must be int.") - def get_variable_days(self, year): - days = super().get_variable_days(year) - days.extend(self.get_chinese_new_year(year)) - return days + return period - @staticmethod - def observance_shift_for_sunday(holiday, calendar): - """ - Taking an existing holiday, return a 'shifted' day to skip on from SUN. + def _get_ical_target_path(self, target_path): """ - return holiday + timedelta(days=1) + Return target path for iCal export. - def get_shifted_holidays(self, dates): - """ - Taking a list of existing holidays, yield a list of 'shifted' days if - the holiday falls on SUN. + Note + ---- + If `target_path` does not have one of the extensions `.ical`, `.ics`, + `.ifb`, or `.icalendar`, the extension `.ics` is appended to the path. + Returns + ------- + None. + Examples + -------- + >>> from calendra.europe import Austria + >>> cal = Austria() + >>> cal._get_ical_target_path('austria') + 'austria.ics' """ - for holiday, label in dates: - if isinstance(holiday, date): - holiday = Holiday(holiday, label) - if holiday.weekday() == SUN: - holiday.observance_shift = self.observance_shift_for_sunday - yield holiday - def get_calendar_holidays(self, year): + if not target_path: + raise ICalExportTargetPathError( + "Incorrect target path. It must not be empty") + + if isdir(target_path): + raise ICalExportTargetPathError( + "Incorrect target path. It must not be a directory" + ) + + ical_extensions = ['.ical', '.ics', '.ifb', '.icalendar'] + if os.path.splitext(target_path)[1] not in ical_extensions: + target_path += '.ics' + return target_path + + def export_to_ical(self, period=[2000, 2030], target_path=None): """ - Take into account the eventual shift to the next MON if any holiday - falls on SUN. + Export the calendar to iCal (RFC 5545) format. + + Parameters + ---------- + period: [int, int] + start and end year (inclusive) of calendar + Default is [2000, 2030] + + target_path: str + the name or path of the exported file. If this argument is missing, + the function will return the ical content. + """ - # Unshifted days are here: - days = super().get_calendar_holidays(year) - if self.shift_sunday_holidays: - days = self.get_shifted_holidays(days) - return days + first_year, last_year = self._get_ical_period(period) + if target_path: + # Generate filename path before calculate the holidays + target_path = self._get_ical_target_path(target_path) + # fetch holidays + holidays = [] + for year in range(first_year, last_year + 1): + holidays.extend(self.holidays(year)) -class ChineseNewYearCalendar(ChineseNewYearMixin, LunarCalendar): - """ - Chinese Calendar, using Chinese New Year computation. - """ - # There are regional exceptions to those week-end days, to define locally. - WEEKEND_DAYS = (SAT, SUN) + # initialize icalendar + ics = [ + 'BEGIN:VCALENDAR', + 'VERSION:2.0', # current RFC5545 version + f'PRODID:-//workalendar//ical {__version__}//EN' + ] + common_timestamp = datetime.utcnow().strftime('%Y%m%dT%H%M%SZ') + dtstamp = 'DTSTAMP;VALUE=DATE-TIME:%s' % common_timestamp + # add an event for each holiday + for holiday in holidays: + date_ = self.get_observed_date(holiday) + ics.extend([ + 'BEGIN:VEVENT', + 'SUMMARY:%s' % holiday.name, + 'DTSTART;VALUE=DATE:%s' % date_.strftime('%Y%m%d'), + dtstamp, + f'UID:{date_}{holiday.name}@peopledoc.github.io/workalendar', + 'END:VEVENT', + ]) -class CalverterMixin: - conversion_method = None - ISLAMIC_HOLIDAYS = () + # add footer + ics.append('END:VCALENDAR\n') # last line with a trailing \n - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.calverter = Calverter() - if self.conversion_method is None: - raise NotImplementedError + # Transform this list into text lines + ics = "\n".join(ics) - def converted(self, year): - conversion_method = getattr( - self.calverter, 'jd_to_%s' % self.conversion_method) - current = date(year, 1, 1) - days = [] - while current.year == year: - julian_day = self.calverter.gregorian_to_jd( - current.year, - current.month, - current.day) - days.append(conversion_method(julian_day)) - current = current + timedelta(days=1) - return days + if target_path: + # save iCal file + with open(target_path, 'w+') as export_file: + export_file.write(ics) + return - def calverted_years(self, year): - converted = self.converted(year) - generator = (y for y, m, d in converted) - return sorted(list(set(generator))) + return ics - def get_islamic_holidays(self): - return self.ISLAMIC_HOLIDAYS - def get_delta_islamic_holidays(self, year): - """ - Return the delta to add/substract according to the year or customs. +class Calendar(CoreCalendar): + """ + The cornerstone of Earth calendars. - By default, to return None or timedelta(days=0) - """ - return None + Take care of the New Years Day, which is almost a worldwide holiday. + """ + include_new_years_day = True + shift_new_years_day = False + include_labour_day = False + labour_day_label = "Labour Day" - def get_variable_days(self, year): - warnings.warn('Please take note that, due to arbitrary decisions, ' - 'this Islamic calendar computation may be wrong.') - days = super().get_variable_days(year) - years = self.calverted_years(year) - conversion_method = getattr( - self.calverter, '%s_to_jd' % self.conversion_method) - for month, day, label in self.get_islamic_holidays(): - for y in years: - jd = conversion_method(y, month, day) - g_year, g_month, g_day = self.calverter.jd_to_gregorian(jd) - holiday = date(g_year, g_month, g_day) + def __init__(self, **kwargs): + super().__init__() - # Only add a delta if necessary - delta = self.get_delta_islamic_holidays(year) - if delta: - holiday += delta + def get_fixed_holidays(self, year): + days = super().get_fixed_holidays(year) + if self.include_new_years_day: + days.insert( + 0, (date(year, 1, 1), "New year") + ) - if holiday.year == year: - days.append((holiday, label)) + if self.include_labour_day: + days.append( + (date(year, 5, 1), self.labour_day_label) + ) return days + def get_variable_days(self, year): + days = super().get_variable_days(year) + new_year = date(year, 1, 1) + if self.include_new_years_day and self.shift_new_years_day: + if new_year.weekday() in self.get_weekend_days(): + days.append(( + self.find_following_working_day(new_year), + "New Year shift")) + return days -class IslamicMixin(SeriesShiftMixin, CalverterMixin): - WEEKEND_DAYS = (FRI, SAT) +class WesternCalendar(WesternMixin, Calendar): + pass - conversion_method = 'islamic' - include_prophet_birthday = False - include_day_after_prophet_birthday = False - include_start_ramadan = False - include_eid_al_fitr = False - length_eid_al_fitr = 1 - eid_al_fitr_label = "Eid al-Fitr" - include_eid_al_adha = False - length_eid_al_adha = 1 - include_day_of_sacrifice = False - day_of_sacrifice_label = "Eid al-Adha" - include_islamic_new_year = False - include_laylat_al_qadr = False - include_nuzul_al_quran = False - def get_islamic_holidays(self): # noqa: C901 - """Return a list of Islamic (month, day, label) for islamic holidays. - Please take note that these dates must be expressed using the Islamic - Calendar""" - days = list(super().get_islamic_holidays()) +class OrthodoxCalendar(OrthodoxMixin, Calendar): + pass - if self.include_islamic_new_year: - days.append((1, 1, "Islamic New Year")) - if self.include_prophet_birthday: - days.append((3, 12, "Prophet's Birthday")) - if self.include_day_after_prophet_birthday: - days.append((3, 13, "Day after Prophet's Birthday")) - if self.include_start_ramadan: - days.append((9, 1, "Start of ramadan")) - if self.include_nuzul_al_quran: - days.append((9, 17, "Nuzul Al-Qur'an")) - if self.include_eid_al_fitr: - for x in range(self.length_eid_al_fitr): - days.append((10, x + 1, self.eid_al_fitr_label)) - if self.include_eid_al_adha: - for x in range(self.length_eid_al_adha): - days.append((12, x + 10, "Eid al-Adha")) - if self.include_day_of_sacrifice: - days.append((12, 10, self.day_of_sacrifice_label)) - if self.include_laylat_al_qadr: - warnings.warn("The Islamic holiday named Laylat al-Qadr is decided" - " by the religious authorities. It is not possible" - " to compute it. You'll have to add it manually.") - return tuple(days) - def get_calendar_holidays(self, year): - self.series_requiring_shifts = [self.eid_al_fitr_label, - self.day_of_sacrifice_label] - return super().get_calendar_holidays(year) +class LunarCalendar(LunarMixin, Calendar): + pass -class JalaliMixin(CalverterMixin): - conversion_method = 'jalali' +class ChineseNewYearCalendar(ChineseNewYearMixin, LunarCalendar): + """ + Chinese Calendar, using Chinese New Year computation. + """ + # There are regional exceptions to those week-end days, to define locally. + WEEKEND_DAYS = (SAT, SUN) class IslamicCalendar(IslamicMixin, Calendar): From 39615cebf3721e04564615a68bda9468ce0923c2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Sep 2022 21:39:09 -0400 Subject: [PATCH 439/447] Restore docstrings and remove LunarCalendar for better alignment with workalendar. --- calendra/core.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/calendra/core.py b/calendra/core.py index 54f93a1e..027d0b35 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -1056,18 +1056,18 @@ def get_variable_days(self, year): class WesternCalendar(WesternMixin, Calendar): - pass + """ + A Christian calendar using Western definition for Easter. + """ class OrthodoxCalendar(OrthodoxMixin, Calendar): - pass - - -class LunarCalendar(LunarMixin, Calendar): - pass + """ + A Christian calendar using Orthodox definition for Easter. + """ -class ChineseNewYearCalendar(ChineseNewYearMixin, LunarCalendar): +class ChineseNewYearCalendar(ChineseNewYearMixin, Calendar): """ Chinese Calendar, using Chinese New Year computation. """ From d1772dcba1ebfacd32c664d53123eaf79188af79 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 1 Sep 2022 21:56:31 -0400 Subject: [PATCH 440/447] Update changelog for 7.1 --- CHANGES.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index a10e810e..d01f339e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,5 @@ -Next ----- +v7.1.0 +------ Incorporate changes from workalendar v14.3.0 (2021-01-15) @@ -23,7 +23,7 @@ Internal - Fix tests when running them on Windows (#607). v7.0.0 -------- +------ New feature From 623a94e5ed9d5474f4a2c5523a45d0fe2321ea70 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Sep 2022 09:41:25 -0400 Subject: [PATCH 441/447] For ical export, honor observed date even when no weekend days are defined. --- calendra/core.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/calendra/core.py b/calendra/core.py index a9f31f8b..8c029168 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -614,7 +614,11 @@ def get_observed_date(self, holiday): return shift(holiday, self) shift = shift or {} delta = rd.relativedelta(**shift) - should_shift = holiday.weekday() in self.get_weekend_days() + try: + weekend_days = self.get_weekend_days() + except NotImplementedError: + weekend_days = () + should_shift = holiday.weekday() in weekend_days shifted = holiday + delta if should_shift else holiday precedent = getattr(holiday, 'observe_after', None) while precedent and shifted <= self.get_observed_date(precedent): From ea140635370f60ff5f52c7bec325d89254987cce Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Sep 2022 09:57:36 -0400 Subject: [PATCH 442/447] Mark China tests as xfail. --- calendra/tests/__init__.py | 15 +++++++++++++++ calendra/tests/test_asia.py | 3 +++ 2 files changed, 18 insertions(+) diff --git a/calendra/tests/__init__.py b/calendra/tests/__init__.py index 29cae303..cee9c8f8 100644 --- a/calendra/tests/__init__.py +++ b/calendra/tests/__init__.py @@ -1,6 +1,7 @@ import os import tempfile import warnings +import functools from datetime import date from pathlib import Path from unittest import TestCase @@ -23,6 +24,19 @@ def setUp(self): self.cal = self.cal_class(**self.kwargs) +def china_xfail(func): + @functools.wraps(func) + def wrapper(self): + try: + func(self) + except Exception: + pytest.xfail("Fails in 2022") + else: + if self.cal_class.__name__ == "China": + pytest.fail("Did not raise") + return wrapper + + class GenericCalendarTest(CoreCalendarTest): test_include_january_1st = True @@ -39,6 +53,7 @@ def test_weekend_days(self): except NotImplementedError: assert False, (self.cal, class_name) + @china_xfail def test_january_1st(self): class_name = self.cal_class.__name__ if class_name in ['Calendar']: diff --git a/calendra/tests/test_asia.py b/calendra/tests/test_asia.py index 075596e4..be7bd0f8 100644 --- a/calendra/tests/test_asia.py +++ b/calendra/tests/test_asia.py @@ -3,6 +3,8 @@ import time from . import GenericCalendarTest +import pytest + from ..asia import ( HongKong, HongKongBank, Japan, JapanBank, Qatar, Singapore, @@ -94,6 +96,7 @@ def test_missing_holiday_year(self): self.cal.holidays_set(2018) china_holidays[2018] = save_2018 + @pytest.mark.xfail(reason="Fails in 2022", strict=True) def test_warning(self): year = date.today().year with patch('warnings.warn') as patched: From fb16c49bab6ac471342d6bc8938e742d943a5bb8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 3 Sep 2022 07:57:13 -0400 Subject: [PATCH 443/447] In SeriesShiftMixin, simply apply a heuristic that all holidays should be observed after earlier holidays. --- calendra/asia/china.py | 4 --- calendra/asia/south_korea.py | 1 - calendra/core.py | 9 ++---- calendra/europe/netherlands.py | 17 ++++++----- calendra/europe/serbia.py | 1 - calendra/holiday.py | 55 +++++----------------------------- 6 files changed, 20 insertions(+), 67 deletions(-) diff --git a/calendra/asia/china.py b/calendra/asia/china.py index fa32525a..8eeee8a1 100644 --- a/calendra/asia/china.py +++ b/calendra/asia/china.py @@ -93,10 +93,6 @@ class China(SeriesShiftMixin, ChineseNewYearCalendar): FIXED_HOLIDAYS = tuple(national_days) include_chinese_new_year_eve = True - series_requiring_shifts = ['Spring Festival', 'Ching Ming Festival', - 'Labour Day Holiday', 'Dragon Boat Festival', - 'Mid-Autumn Festival', 'New year', - 'National Day'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/calendra/asia/south_korea.py b/calendra/asia/south_korea.py index 0a735387..27940845 100644 --- a/calendra/asia/south_korea.py +++ b/calendra/asia/south_korea.py @@ -19,7 +19,6 @@ class SouthKorea(SeriesShiftMixin, ChineseNewYearCalendar): chinese_new_year_eve_label = "Korean New Year's Day" include_chinese_second_day = True chinese_second_day_label = "Korean New Year's Day" - series_requiring_shifts = ["Korean New Year's Day", "Midautumn Festival"] def get_variable_days(self, year): days = super().get_variable_days(year) diff --git a/calendra/core.py b/calendra/core.py index 87b2b603..7cd25df1 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -7,6 +7,7 @@ from datetime import date, timedelta, datetime from pathlib import Path import sys +import functools import convertdate from dateutil import easter @@ -232,7 +233,7 @@ def get_variable_days(self, year): # noqa date(year, 12, 26), self.boxing_day_label, indication="Day after Christmas", - observe_after=christmas + observe_after=christmas, ) days.append(boxing_day) if self.include_ascension: @@ -527,11 +528,6 @@ def get_islamic_holidays(self): # noqa: C901 " to compute it. You'll have to add it manually.") return tuple(days) - def get_calendar_holidays(self, year): - self.series_requiring_shifts = [self.eid_al_fitr_label, - self.day_of_sacrifice_label] - return super().get_calendar_holidays(year) - class CoreCalendar: @@ -597,6 +593,7 @@ def get_holiday_label(self, day): day = cleaned_date(day) return {day: label for day, label in self.holidays(day.year)}.get(day) + @functools.lru_cache() def get_observed_date(self, holiday): """ The date the holiday is observed for this calendar. If the holiday diff --git a/calendra/europe/netherlands.py b/calendra/europe/netherlands.py index ca7b1f2f..f9bfe73a 100644 --- a/calendra/europe/netherlands.py +++ b/calendra/europe/netherlands.py @@ -2,6 +2,7 @@ from ..core import WesternCalendar, SUN, ISO_SAT from ..core import SeriesShiftMixin from ..registry_tools import iso_register +from ..core import Holiday @iso_register('NL') @@ -15,7 +16,6 @@ class Netherlands(SeriesShiftMixin, WesternCalendar): include_whit_sunday = True include_whit_monday = True include_boxing_day = True - series_requiring_shifts = ["Carnival"] FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( (5, 5, "Liberation Day"), @@ -120,11 +120,6 @@ class NetherlandsWithSchoolHolidays(Netherlands): Data source and regulating body: https://www.rijksoverheid.nl/onderwerpen/schoolvakanties/overzicht-schoolvakanties-per-schooljaar """ - series_requiring_shifts = [ - "Carnival", "Carnival holiday", - "Fall holiday", "Christmas holiday", "Spring holiday", "May holiday", - "Summer holiday" - ] def __init__(self, region, carnival_instead_of_spring=False, **kwargs): """ Set up a calendar incl. school holidays for a specific region @@ -166,7 +161,13 @@ def get_fall_holidays(self, year): (start + timedelta(days=i), "Fall holiday") for i in range(n_days) ] - def get_christmas_holidays(self, year, in_december=True, in_january=True): + def get_christmas_holidays(self, year): + return [ + Holiday._from_resolved_definition(defn, observance_shift=None) + for defn in self._get_christmas_holidays(year) + ] + + def _get_christmas_holidays(self, year, in_december=True, in_january=True): """ Return Christmas holidays @@ -188,7 +189,7 @@ def get_christmas_holidays(self, year, in_december=True, in_january=True): if in_january: dates.extend( - self.get_christmas_holidays(year, in_december=False) + self._get_christmas_holidays(year, in_december=False) ) return dates diff --git a/calendra/europe/serbia.py b/calendra/europe/serbia.py index ae2bd3a0..def2db47 100644 --- a/calendra/europe/serbia.py +++ b/calendra/europe/serbia.py @@ -22,4 +22,3 @@ class Serbia(SeriesShiftMixin, OrthodoxCalendar): include_easter_sunday = True include_easter_monday = True include_christmas = False - series_requiring_shifts = ["Statehood Day"] diff --git a/calendra/holiday.py b/calendra/holiday.py index 77e0c596..f16e0ef1 100644 --- a/calendra/holiday.py +++ b/calendra/holiday.py @@ -1,6 +1,5 @@ import itertools from datetime import date, timedelta -from typing import Optional, List from more_itertools import recipes @@ -92,12 +91,12 @@ def _from_fixed_definition(cls, item): return item @classmethod - def _from_resolved_definition(cls, item): + def _from_resolved_definition(cls, item, **kwargs): """For backward compatibility, load Holiday object from a two-tuple or existing Holiday instance. """ if isinstance(item, tuple): - item = Holiday(*item) + item = Holiday(*item, **kwargs) return item @@ -105,54 +104,16 @@ class SeriesShiftMixin: """ "Series" holidays like the two Islamic Eid's or Chinese Spring Festival span multiple days. If one of these days encounters a non-zero observance_shift, - we need to apply that shift to all subsequent members of the series. - - Packagin as a standalone Mixin ensure that the logic can be applied as - needed *after* any default shift is applied. - """ - series_requiring_shifts: Optional[List[str]] = None - """ - A list of all holiday labels that require series shifting to be applied. + apply that shift to all subsequent members of the series. """ def get_calendar_holidays(self, year): """ - The point at which any shift occurs is year-specific. + Ensure that all events are observed in the order indicated. """ days = super().get_calendar_holidays(year) - series_shift = {series: None for series in self.series_requiring_shifts} - holidays = [] - for holiday, label in days: - # - # Make a year-specific copy in case we have to attach a shift. - # - holiday = Holiday(holiday, label) - # - # For either Eid series, apply the shift to all days in the - # series after the first shift. - # - if label in series_shift: - shifted = self.get_observed_date(holiday) - if series_shift[holiday.name] is None and shifted.day != holiday.day: - - def observance_shift_for_series(holiday, calendar): - """ - Taking an existing holiday, return a 'shifted' day based - on delta in the current year's closure. - """ - return holiday + delta - - delta = date(shifted.year, shifted.month, shifted.day) - \ - date(holiday.year, holiday.month, holiday.day) - # - # Learn the observance_shift for all subsequent days in the - # series. - # - series_shift[holiday.name] = observance_shift_for_series - elif series_shift[holiday.name] is not None: - # - # Apply the learned observance_shift. - # - holiday.observance_shift = series_shift[holiday.name] - holidays.append(holiday) + holidays = sorted(map(Holiday._from_resolved_definition, days)) + from more_itertools import pairwise + for a, b in pairwise(holidays): + b.observe_after = a return holidays From d5579f549da1a00f928943a4644dca2d59146a91 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 3 Sep 2022 13:11:58 -0400 Subject: [PATCH 444/447] Simply set observance shift to None for Netherlands. --- calendra/europe/netherlands.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/calendra/europe/netherlands.py b/calendra/europe/netherlands.py index f9bfe73a..7fb1b888 100644 --- a/calendra/europe/netherlands.py +++ b/calendra/europe/netherlands.py @@ -2,7 +2,6 @@ from ..core import WesternCalendar, SUN, ISO_SAT from ..core import SeriesShiftMixin from ..registry_tools import iso_register -from ..core import Holiday @iso_register('NL') @@ -17,6 +16,8 @@ class Netherlands(SeriesShiftMixin, WesternCalendar): include_whit_monday = True include_boxing_day = True + observance_shift = None + FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( (5, 5, "Liberation Day"), ) @@ -161,13 +162,7 @@ def get_fall_holidays(self, year): (start + timedelta(days=i), "Fall holiday") for i in range(n_days) ] - def get_christmas_holidays(self, year): - return [ - Holiday._from_resolved_definition(defn, observance_shift=None) - for defn in self._get_christmas_holidays(year) - ] - - def _get_christmas_holidays(self, year, in_december=True, in_january=True): + def get_christmas_holidays(self, year, in_december=True, in_january=True): """ Return Christmas holidays @@ -189,7 +184,7 @@ def _get_christmas_holidays(self, year, in_december=True, in_january=True): if in_january: dates.extend( - self._get_christmas_holidays(year, in_december=False) + self.get_christmas_holidays(year, in_december=False) ) return dates From 505d34c2bc7d0ad7d8cfd194a2b84673a659289a Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 3 Sep 2022 13:27:08 -0400 Subject: [PATCH 445/447] Apply SeriesShiftMixin to all calendars. --- calendra/asia/china.py | 4 ++-- calendra/asia/south_korea.py | 4 ++-- calendra/core.py | 9 +++------ calendra/europe/netherlands.py | 3 +-- calendra/europe/serbia.py | 4 ++-- calendra/oceania/new_zealand.py | 5 ----- 6 files changed, 10 insertions(+), 19 deletions(-) diff --git a/calendra/asia/china.py b/calendra/asia/china.py index 8eeee8a1..7fafb465 100644 --- a/calendra/asia/china.py +++ b/calendra/asia/china.py @@ -1,7 +1,7 @@ from datetime import date import warnings -from ..core import ChineseNewYearCalendar, SeriesShiftMixin +from ..core import ChineseNewYearCalendar from ..registry_tools import iso_register from ..exceptions import CalendarError @@ -84,7 +84,7 @@ @iso_register('CN') -class China(SeriesShiftMixin, ChineseNewYearCalendar): +class China(ChineseNewYearCalendar): "China" # WARNING: Support 2018-2022 currently, need update every year. shift_new_years_day = True diff --git a/calendra/asia/south_korea.py b/calendra/asia/south_korea.py index 27940845..2e194b66 100644 --- a/calendra/asia/south_korea.py +++ b/calendra/asia/south_korea.py @@ -1,9 +1,9 @@ -from ..core import ChineseNewYearCalendar, SeriesShiftMixin +from ..core import ChineseNewYearCalendar from ..registry_tools import iso_register @iso_register('KR') -class SouthKorea(SeriesShiftMixin, ChineseNewYearCalendar): +class SouthKorea(ChineseNewYearCalendar): "South Korea" FIXED_HOLIDAYS = ChineseNewYearCalendar.FIXED_HOLIDAYS + ( (3, 1, "Independence Day"), diff --git a/calendra/core.py b/calendra/core.py index 7cd25df1..caf0574f 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -233,7 +233,6 @@ def get_variable_days(self, year): # noqa date(year, 12, 26), self.boxing_day_label, indication="Day after Christmas", - observe_after=christmas, ) days.append(boxing_day) if self.include_ascension: @@ -400,9 +399,7 @@ def get_shifted_holidays(self, dates): Taking a list of existing holidays, yield a list of 'shifted' days if the holiday falls on SUN. """ - for holiday, label in dates: - if isinstance(holiday, date): - holiday = Holiday(holiday, label) + for holiday in dates: if holiday.weekday() == SUN: holiday.observance_shift = self.observance_shift_for_sunday yield holiday @@ -478,7 +475,7 @@ def get_variable_days(self, year): return days -class IslamicMixin(SeriesShiftMixin, CalverterMixin): +class IslamicMixin(CalverterMixin): WEEKEND_DAYS = (FRI, SAT) @@ -1073,7 +1070,7 @@ def export_to_ical(self, period=[2000, 2030], target_path=None): return ics -class Calendar(CoreCalendar): +class Calendar(SeriesShiftMixin, CoreCalendar): """ The cornerstone of Earth calendars. diff --git a/calendra/europe/netherlands.py b/calendra/europe/netherlands.py index 7fb1b888..1806de1b 100644 --- a/calendra/europe/netherlands.py +++ b/calendra/europe/netherlands.py @@ -1,11 +1,10 @@ from datetime import date, timedelta from ..core import WesternCalendar, SUN, ISO_SAT -from ..core import SeriesShiftMixin from ..registry_tools import iso_register @iso_register('NL') -class Netherlands(SeriesShiftMixin, WesternCalendar): +class Netherlands(WesternCalendar): 'Netherlands' include_good_friday = True diff --git a/calendra/europe/serbia.py b/calendra/europe/serbia.py index def2db47..4cc05f21 100644 --- a/calendra/europe/serbia.py +++ b/calendra/europe/serbia.py @@ -1,9 +1,9 @@ -from ..core import OrthodoxCalendar, SeriesShiftMixin +from ..core import OrthodoxCalendar from ..registry_tools import iso_register @iso_register('RS') -class Serbia(SeriesShiftMixin, OrthodoxCalendar): +class Serbia(OrthodoxCalendar): 'Serbia' FIXED_HOLIDAYS = OrthodoxCalendar.FIXED_HOLIDAYS + ( diff --git a/calendra/oceania/new_zealand.py b/calendra/oceania/new_zealand.py index c05619e5..43163d59 100644 --- a/calendra/oceania/new_zealand.py +++ b/calendra/oceania/new_zealand.py @@ -7,10 +7,6 @@ from ..registry_tools import iso_register -def _by_name(holidays): - return {day.name: day for day in holidays if hasattr(day, 'name')} - - @iso_register("NZ") class NewZealand(WesternCalendar, ChristianMixin): "New Zealand" @@ -44,7 +40,6 @@ def get_variable_days(self, year): days.append(Holiday( date(year, 1, 2), "Day after New Year's Day", - observe_after=_by_name(days)["New year"], )) days.append(self.get_queens_birthday(year)) days.append(self.get_labour_day(year)) From 3d71fc78fab1b428a6327f28f125d29188da185e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 3 Sep 2022 14:05:03 -0400 Subject: [PATCH 446/447] Attempting to simplify the holiday shift for Chinese new year. Doesn't work because observe_after doesn't work for callable shift. --- calendra/asia/hong_kong.py | 1 + calendra/core.py | 26 +++----------------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/calendra/asia/hong_kong.py b/calendra/asia/hong_kong.py index 52964f59..d073c3c7 100644 --- a/calendra/asia/hong_kong.py +++ b/calendra/asia/hong_kong.py @@ -33,6 +33,7 @@ class HongKong(WesternMixin, ChineseNewYearCalendar): chinese_third_day_label = "Third day of Chinese Lunar New Year" shift_sunday_holidays = True # Except CNY which rolls to Saturday shift_start_cny_sunday = False # Prior to 2011 this was True + shift_new_years_day = True def get_variable_days(self, year): """ diff --git a/calendra/core.py b/calendra/core.py index caf0574f..b7fb3d53 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -388,32 +388,12 @@ def get_variable_days(self, year): return days @staticmethod - def observance_shift_for_sunday(holiday, calendar): + def observance_shift(holiday, calendar): """ Taking an existing holiday, return a 'shifted' day to skip on from SUN. """ - return holiday + timedelta(days=1) - - def get_shifted_holidays(self, dates): - """ - Taking a list of existing holidays, yield a list of 'shifted' days if - the holiday falls on SUN. - """ - for holiday in dates: - if holiday.weekday() == SUN: - holiday.observance_shift = self.observance_shift_for_sunday - yield holiday - - def get_calendar_holidays(self, year): - """ - Take into account the eventual shift to the next MON if any holiday - falls on SUN. - """ - # Unshifted days are here: - days = super().get_calendar_holidays(year) - if self.shift_sunday_holidays: - days = self.get_shifted_holidays(days) - return days + do_shift = calendar.shift_sunday_holidays and holiday.weekday() == SUN + return holiday + timedelta(days=1) * do_shift class CalverterMixin: From b80cb9eb65a11da25b9b1bfe1fa5625ba3f14b97 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 3 Sep 2022 18:49:41 -0400 Subject: [PATCH 447/447] Honor observance ordering for callable observance shift. --- calendra/core.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/calendra/core.py b/calendra/core.py index b7fb3d53..97f6ce24 100644 --- a/calendra/core.py +++ b/calendra/core.py @@ -584,15 +584,16 @@ def get_observed_date(self, holiday): # observance_shift may be overridden in the holiday itself shift = getattr(holiday, 'observance_shift', self.observance_shift) if callable(shift): - return shift(holiday, self) - shift = shift or {} - delta = rd.relativedelta(**shift) - try: - weekend_days = self.get_weekend_days() - except NotImplementedError: - weekend_days = () - should_shift = holiday.weekday() in weekend_days - shifted = holiday + delta if should_shift else holiday + shifted = shift(holiday, self) + else: + shift = shift or {} + delta = rd.relativedelta(**shift) + try: + weekend_days = self.get_weekend_days() + except NotImplementedError: + weekend_days = () + should_shift = holiday.weekday() in weekend_days + shifted = holiday + delta if should_shift else holiday precedent = getattr(holiday, 'observe_after', None) while precedent and shifted <= self.get_observed_date(precedent): shifted += timedelta(days=1)