From 2deaa94b38f63804f45142089b5911dd4e4230e6 Mon Sep 17 00:00:00 2001 From: rsb-23 <57601627+rsb-23@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:05:58 +0530 Subject: [PATCH 1/2] Linter fixes --- .pre-commit-config.yaml | 22 + requirements-dev.txt | 2 + setup.cfg | 9 + setup.py | 61 +-- tests.py | 664 +++++++++++------------- vobject/__init__.py | 6 +- vobject/base.py | 271 +++++----- vobject/behavior.py | 13 +- vobject/change_tz.py | 27 +- vobject/hcalendar.py | 49 +- vobject/icalendar.py | 1081 +++++++++++++++++++++------------------ vobject/ics_diff.py | 50 +- vobject/vcard.py | 159 +++--- vobject/win32tz.py | 70 ++- 14 files changed, 1286 insertions(+), 1198 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 setup.cfg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..25ee2cd --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,22 @@ +repos: + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort + + - repo: https://github.com/psf/black + rev: 24.2.0 + hooks: + - id: black + args: [-l, "120", -C] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: no-commit-to-branch + args: [-b, main, -b, master] + - repo: https://github.com/PyCQA/flake8 + rev: 7.0.0 + hooks: + - id: flake8 diff --git a/requirements-dev.txt b/requirements-dev.txt index a0350ad..28af6eb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,3 +2,5 @@ coverage python-dateutil >= 2.4.0 setuptools wheel +pre-commit +black diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..66e5a12 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,9 @@ +[flake8] +max-line-length = 120 +ignore = E203, E266, E501, W503 +exclude = .git, __pycache__, venv +per-file-ignores = */__init__.py: F401 + +[isort] +profile = black +multi_line_output = 3 diff --git a/setup.py b/setup.py index e9e5a76..76bd4f3 100755 --- a/setup.py +++ b/setup.py @@ -23,37 +23,32 @@ - http://vobject.skyhouseconsulting.com/history.html """ -from setuptools import setup, find_packages - -doclines = (__doc__ or '').splitlines() - -setup(name = "vobject", - version = "0.9.7", - author = "Jeffrey Harris", - author_email = "jeffrey@osafoundation.org", - maintainer = "David Arnold", - maintainer_email="davida@pobox.com", - license = "Apache", - zip_safe = True, - url = "http://py-vobject.github.io/vobject/", - download_url = 'https://github.com/py-vobject/vobject/tarball/0.9.7', - bugtrack_url = "https://github.com/py-vobject/vobject/issues", - entry_points = { - 'console_scripts': [ - 'ics_diff = vobject.ics_diff:main', - 'change_tz = vobject.change_tz:main' - ] - }, - include_package_data = True, - install_requires = ['python-dateutil >= 2.4.0', 'six'], - platforms = ["any"], - packages = find_packages(), - description = "A full-featured Python package for parsing and creating " - "iCalendar and vCard files", - long_description = "\n".join(doclines[2:]), - keywords = ['vobject', 'icalendar', 'vcard', 'ics', 'vcs', 'hcalendar'], - test_suite="tests", - classifiers = """ +from setuptools import find_packages, setup + +doclines = (__doc__ or "").splitlines() + +setup( + name="vobject", + version="0.9.7", + author="Jeffrey Harris", + author_email="jeffrey@osafoundation.org", + maintainer="David Arnold", + maintainer_email="davida@pobox.com", + license="Apache", + zip_safe=True, + url="http://py-vobject.github.io/vobject/", + download_url="https://github.com/py-vobject/vobject/tarball/0.9.7", + bugtrack_url="https://github.com/py-vobject/vobject/issues", + entry_points={"console_scripts": ["ics_diff = vobject.ics_diff:main", "change_tz = vobject.change_tz:main"]}, + include_package_data=True, + install_requires=["python-dateutil >= 2.4.0", "six"], + platforms=["any"], + packages=find_packages(), + description="A full-featured Python package for parsing and creating " "iCalendar and vCard files", + long_description="\n".join(doclines[2:]), + keywords=["vobject", "icalendar", "vcard", "ics", "vcs", "hcalendar"], + test_suite="tests", + classifiers=""" Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: Developers @@ -63,5 +58,5 @@ Programming Language :: Python Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Topic :: Text Processing""".strip().splitlines() - ) + Topic :: Text Processing""".strip().splitlines(), +) diff --git a/tests.py b/tests.py index 7db519e..de9ae03 100644 --- a/tests.py +++ b/tests.py @@ -3,29 +3,32 @@ from __future__ import print_function import datetime -import dateutil +import json import re import sys import unittest -import json +import dateutil +from dateutil.rrule import MONTHLY, WEEKLY, rrule, rruleset from dateutil.tz import tzutc -from dateutil.rrule import rrule, rruleset, WEEKLY, MONTHLY - -from vobject import base, iCalendar -from vobject import icalendar - -from vobject.base import __behaviorRegistry as behavior_registry -from vobject.base import ContentLine, parseLine, ParseError -from vobject.base import readComponents, textLineToContentLine +from vobject import base, iCalendar, icalendar +from vobject.base import ContentLine, ParseError +from vobject.base import __behaviorRegistry as BehaviorRegistry +from vobject.base import parseLine, readComponents, textLineToContentLine from vobject.change_tz import change_tz - -from vobject.icalendar import MultiDateBehavior, PeriodBehavior, \ - RecurringComponent, utc -from vobject.icalendar import parseDtstart, stringToTextValues, \ - stringToPeriod, timedeltaToString - +from vobject.icalendar import ( + MultiDateBehavior, + PeriodBehavior, + RecurringComponent, + parseDtstart, + stringToPeriod, + stringToTextValues, + timedeltaToString, + utc, +) + +behavior_registry = BehaviorRegistry two_hours = datetime.timedelta(hours=2) @@ -33,14 +36,14 @@ def get_test_file(path): """ Helper function to open and read test files. """ - filepath = "test_files/{}".format(path) + filepath = f"test_files/{path}" if sys.version_info[0] < 3: # On python 2, this library operates on bytes. - f = open(filepath, 'r') + f = open(filepath, "r") else: # On python 3, it operates on unicode. We need to specify an encoding # for systems for which the preferred encoding isn't utf-8 (e.g windows) - f = open(filepath, 'r', encoding='utf-8') + f = open(filepath, "r", encoding="utf-8") text = f.read() f.close() return text @@ -50,6 +53,7 @@ class TestCalendarSerializing(unittest.TestCase): """ Test creating an iCalendar file """ + max_diff = None def test_scratchbuild(self): @@ -57,25 +61,21 @@ def test_scratchbuild(self): CreateCalendar 2.0 format from scratch """ test_cal = get_test_file("simple_2_0_test.ics") - cal = base.newFromBehavior('vcalendar', '2.0') - cal.add('vevent') - cal.vevent.add('dtstart').value = datetime.datetime(2006, 5, 9) - cal.vevent.add('description').value = "Test event" - cal.vevent.add('created').value = \ - datetime.datetime(2006, 1, 1, 10, - tzinfo=dateutil.tz.tzical( - "test_files/timezones.ics").get('US/Pacific')) - cal.vevent.add('uid').value = "Not very random UID" - cal.vevent.add('dtstamp').value = datetime.datetime(2017, 6, 26, 0, tzinfo=tzutc()) - - cal.vevent.add('attendee').value = 'mailto:froelich@example.com' - cal.vevent.attendee.params['CN'] = ['Fröhlich'] + cal = base.newFromBehavior("vcalendar", "2.0") + cal.add("vevent") + cal.vevent.add("dtstart").value = datetime.datetime(2006, 5, 9) + cal.vevent.add("description").value = "Test event" + cal.vevent.add("created").value = datetime.datetime( + 2006, 1, 1, 10, tzinfo=dateutil.tz.tzical("test_files/timezones.ics").get("US/Pacific") + ) + cal.vevent.add("uid").value = "Not very random UID" + cal.vevent.add("dtstamp").value = datetime.datetime(2017, 6, 26, 0, tzinfo=tzutc()) + + cal.vevent.add("attendee").value = "mailto:froelich@example.com" + cal.vevent.attendee.params["CN"] = ["Fröhlich"] # Note we're normalizing line endings, because no one got time for that. - self.assertEqual( - cal.serialize().replace('\r\n', '\n'), - test_cal.replace('\r\n', '\n') - ) + self.assertEqual(cal.serialize().replace("\r\n", "\n"), test_cal.replace("\r\n", "\n")) def test_unicode(self): """ @@ -86,20 +86,14 @@ def test_unicode(self): vevent2 = base.readOne(vevent.serialize()) self.assertEqual(str(vevent), str(vevent2)) - self.assertEqual( - vevent.summary.value, - 'The title こんにちはキティ' - ) + self.assertEqual(vevent.summary.value, "The title こんにちはキティ") if sys.version_info[0] < 3: - test_cal = test_cal.decode('utf-8') + test_cal = test_cal.decode("utf-8") vevent = base.readOne(test_cal).vevent vevent2 = base.readOne(vevent.serialize()) self.assertEqual(str(vevent), str(vevent2)) - self.assertEqual( - vevent.summary.value, - u'The title こんにちはキティ' - ) + self.assertEqual(vevent.summary.value, "The title こんにちはキティ") def test_wrapping(self): """ @@ -108,36 +102,27 @@ def test_wrapping(self): test_journal = get_test_file("journal.ics") vobj = base.readOne(test_journal) vjournal = base.readOne(vobj.serialize()) - self.assertTrue('Joe, Lisa, and Bob' in vjournal.description.value) - self.assertTrue('Tuesday.\n2.' in vjournal.description.value) + self.assertTrue("Joe, Lisa, and Bob" in vjournal.description.value) + self.assertTrue("Tuesday.\n2." in vjournal.description.value) def test_multiline(self): """ Multi-text serialization test """ - category = base.newFromBehavior('categories') - category.value = ['Random category'] - self.assertEqual( - category.serialize().strip(), - "CATEGORIES:Random category" - ) + category = base.newFromBehavior("categories") + category.value = ["Random category"] + self.assertEqual(category.serialize().strip(), "CATEGORIES:Random category") - category.value.append('Other category') - self.assertEqual( - category.serialize().strip(), - "CATEGORIES:Random category,Other category" - ) + category.value.append("Other category") + self.assertEqual(category.serialize().strip(), "CATEGORIES:Random category,Other category") def test_semicolon_separated(self): """ Semi-colon separated multi-text serialization test """ - request_status = base.newFromBehavior('request-status') - request_status.value = ['5.1', 'Service unavailable'] - self.assertEqual( - request_status.serialize().strip(), - "REQUEST-STATUS:5.1;Service unavailable" - ) + request_status = base.newFromBehavior("request-status") + request_status.value = ["5.1", "Service unavailable"] + self.assertEqual(request_status.serialize().strip(), "REQUEST-STATUS:5.1;Service unavailable") @staticmethod def test_unicode_multiline(): @@ -145,12 +130,14 @@ def test_unicode_multiline(): Test multiline unicode characters """ cal = iCalendar() - cal.add('method').value = 'REQUEST' - cal.add('vevent') - cal.vevent.add('created').value = datetime.datetime.now() - cal.vevent.add('summary').value = 'Классное событие' - cal.vevent.add('description').value = ('Классное событие Классное событие Классное событие Классное событие ' - 'Классное событие Классsdssdное событие') + cal.add("method").value = "REQUEST" + cal.add("vevent") + cal.vevent.add("created").value = datetime.datetime.now() + cal.vevent.add("summary").value = "Классное событие" + cal.vevent.add("description").value = ( + "Классное событие Классное событие Классное событие Классное событие " + "Классное событие Классsdssdное событие" + ) # json tries to encode as utf-8 and it would break if some chars could not be encoded json.dumps(cal.serialize()) @@ -185,7 +172,7 @@ def test_ical_to_hcal(): event2.add('dtend').value = event2.dtstart.value + datetime.timedelta(days = 6) hcal = cal.serialize() """ - #self.assertEqual( + # self.assertEqual( # str(hcal), # """ # @@ -203,13 +190,14 @@ def test_ical_to_hcal(): #
The greatest thing ever!
#
# """ - #) + # ) class TestBehaviors(unittest.TestCase): """ Test Behaviors """ + def test_general_behavior(self): """ Tests for behavior registry, getting and creating a behavior. @@ -217,32 +205,70 @@ def test_general_behavior(self): # Check expected behavior registry. self.assertEqual( sorted(behavior_registry.keys()), - ['', 'ACTION', 'ADR', 'AVAILABLE', 'BUSYTYPE', 'CALSCALE', - 'CATEGORIES', 'CLASS', 'COMMENT', 'COMPLETED', 'CONTACT', - 'CREATED', 'DAYLIGHT', 'DESCRIPTION', 'DTEND', 'DTSTAMP', - 'DTSTART', 'DUE', 'DURATION', 'EXDATE', 'EXRULE', 'FN', 'FREEBUSY', - 'LABEL', 'LAST-MODIFIED', 'LOCATION', 'METHOD', 'N', 'ORG', - 'PHOTO', 'PRODID', 'RDATE', 'RECURRENCE-ID', 'RELATED-TO', - 'REQUEST-STATUS', 'RESOURCES', 'RRULE', 'STANDARD', 'STATUS', - 'SUMMARY', 'TRANSP', 'TRIGGER', 'UID', 'VALARM', 'VAVAILABILITY', - 'VCALENDAR', 'VCARD', 'VEVENT', 'VFREEBUSY', 'VJOURNAL', - 'VTIMEZONE', 'VTODO'] + [ + "", + "ACTION", + "ADR", + "AVAILABLE", + "BUSYTYPE", + "CALSCALE", + "CATEGORIES", + "CLASS", + "COMMENT", + "COMPLETED", + "CONTACT", + "CREATED", + "DAYLIGHT", + "DESCRIPTION", + "DTEND", + "DTSTAMP", + "DTSTART", + "DUE", + "DURATION", + "EXDATE", + "EXRULE", + "FN", + "FREEBUSY", + "LABEL", + "LAST-MODIFIED", + "LOCATION", + "METHOD", + "N", + "ORG", + "PHOTO", + "PRODID", + "RDATE", + "RECURRENCE-ID", + "RELATED-TO", + "REQUEST-STATUS", + "RESOURCES", + "RRULE", + "STANDARD", + "STATUS", + "SUMMARY", + "TRANSP", + "TRIGGER", + "UID", + "VALARM", + "VAVAILABILITY", + "VCALENDAR", + "VCARD", + "VEVENT", + "VFREEBUSY", + "VJOURNAL", + "VTIMEZONE", + "VTODO", + ], ) # test get_behavior - behavior = base.getBehavior('VCALENDAR') - self.assertEqual( - str(behavior), - "" - ) + behavior = base.getBehavior("VCALENDAR") + self.assertEqual(str(behavior), "") self.assertTrue(behavior.isComponent) - self.assertEqual( - base.getBehavior("invalid_name"), - None - ) + self.assertEqual(base.getBehavior("invalid_name"), None) # test for ContentLine (not a component) - non_component_behavior = base.getBehavior('RDATE') + non_component_behavior = base.getBehavior("RDATE") self.assertFalse(non_component_behavior.isComponent) def test_MultiDateBehavior(self): @@ -252,62 +278,62 @@ def test_MultiDateBehavior(self): parseRDate = MultiDateBehavior.transformToNative self.assertEqual( str(parseRDate(textLineToContentLine("RDATE;VALUE=DATE:19970304,19970504,19970704,19970904"))), - "" + "", ) self.assertEqual( - str(parseRDate(textLineToContentLine("RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z,19960404T010000Z/PT3H"))), - "" + str( + parseRDate( + textLineToContentLine("RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z,19960404T010000Z/PT3H") + ) + ), + "", ) def test_periodBehavior(self): """ Test PeriodBehavior """ - line = ContentLine('test', [], '', isNative=True) + line = ContentLine("test", [], "", isNative=True) line.behavior = PeriodBehavior line.value = [(datetime.datetime(2006, 2, 16, 10), two_hours)] + self.assertEqual(line.transformFromNative().value, "20060216T100000/PT2H") self.assertEqual( - line.transformFromNative().value, - '20060216T100000/PT2H' - ) - self.assertEqual( - line.transformToNative().value, - [(datetime.datetime(2006, 2, 16, 10, 0), - datetime.timedelta(0, 7200))] + line.transformToNative().value, [(datetime.datetime(2006, 2, 16, 10, 0), datetime.timedelta(0, 7200))] ) line.value.append((datetime.datetime(2006, 5, 16, 10), two_hours)) - self.assertEqual( - line.serialize().strip(), - 'TEST:20060216T100000/PT2H,20060516T100000/PT2H' - ) + self.assertEqual(line.serialize().strip(), "TEST:20060216T100000/PT2H,20060516T100000/PT2H") class TestVTodo(unittest.TestCase): """ VTodo Tests """ + def test_vtodo(self): """ Test VTodo """ vtodo = get_test_file("vtodo.ics") obj = base.readOne(vtodo) - obj.vtodo.add('completed') - obj.vtodo.completed.value = datetime.datetime(2015,5,5,13,30) - self.assertEqual(obj.vtodo.completed.serialize()[0:23], - 'COMPLETED:20150505T1330') + obj.vtodo.add("completed") + obj.vtodo.completed.value = datetime.datetime(2015, 5, 5, 13, 30) + self.assertEqual(obj.vtodo.completed.serialize()[:23], "COMPLETED:20150505T1330") obj = base.readOne(obj.serialize()) - self.assertEqual(obj.vtodo.completed.value, - datetime.datetime(2015,5,5,13,30)) + self.assertEqual(obj.vtodo.completed.value, datetime.datetime(2015, 5, 5, 13, 30)) class TestVobject(unittest.TestCase): """ VObject Tests """ + max_diff = None @classmethod @@ -331,26 +357,33 @@ def test_parseLine(self): """ Test line parsing """ - self.assertEqual(parseLine("BLAH:"), ('BLAH', [], '', None)) + self.assertEqual(parseLine("BLAH:"), ("BLAH", [], "", None)) self.assertEqual( parseLine("RDATE:VALUE=DATE:19970304,19970504,19970704,19970904"), - ('RDATE', [], 'VALUE=DATE:19970304,19970504,19970704,19970904', None) + ("RDATE", [], "VALUE=DATE:19970304,19970504,19970704,19970904", None), ) self.assertEqual( - parseLine('DESCRIPTION;ALTREP="http://www.wiz.org":The Fall 98 Wild Wizards Conference - - Las Vegas, NV, USA'), - ('DESCRIPTION', [['ALTREP', 'http://www.wiz.org']], 'The Fall 98 Wild Wizards Conference - - Las Vegas, NV, USA', None) + parseLine( + 'DESCRIPTION;ALTREP="http://www.wiz.org":The Fall 98 Wild Wizards Conference - - Las Vegas, NV, USA' + ), + ( + "DESCRIPTION", + [["ALTREP", "http://www.wiz.org"]], + "The Fall 98 Wild Wizards Conference - - Las Vegas, NV, USA", + None, + ), ) self.assertEqual( parseLine("EMAIL;PREF;INTERNET:john@nowhere.com"), - ('EMAIL', [['PREF'], ['INTERNET']], 'john@nowhere.com', None) + ("EMAIL", [["PREF"], ["INTERNET"]], "john@nowhere.com", None), ) self.assertEqual( parseLine('EMAIL;TYPE="blah",hah;INTERNET="DIGI",DERIDOO:john@nowhere.com'), - ('EMAIL', [['TYPE', 'blah', 'hah'], ['INTERNET', 'DIGI', 'DERIDOO']], 'john@nowhere.com', None) + ("EMAIL", [["TYPE", "blah", "hah"], ["INTERNET", "DIGI", "DERIDOO"]], "john@nowhere.com", None), ) self.assertEqual( - parseLine('item1.ADR;type=HOME;type=pref:;;Reeperbahn 116;Hamburg;;20359;'), - ('ADR', [['type', 'HOME'], ['type', 'pref']], ';;Reeperbahn 116;Hamburg;;20359;', 'item1') + parseLine("item1.ADR;type=HOME;type=pref:;;Reeperbahn 116;Hamburg;;20359;"), + ("ADR", [["type", "HOME"], ["type", "pref"]], ";;Reeperbahn 116;Hamburg;;20359;", "item1"), ) self.assertRaises(ParseError, parseLine, ":") @@ -359,6 +392,7 @@ class TestGeneralFileParsing(unittest.TestCase): """ General tests for parsing ics files. """ + def test_readOne(self): """ Test reading first component of ics @@ -367,12 +401,10 @@ def test_readOne(self): silly = base.readOne(cal) self.assertEqual( str(silly), - ", , ]>" - ) - self.assertEqual( - str(silly.stuff), - "" + ", , ]>", ) + self.assertEqual(str(silly.stuff), "") def test_importing(self): """ @@ -380,35 +412,16 @@ def test_importing(self): """ cal = get_test_file("standard_test.ics") c = base.readOne(cal, validate=True) - self.assertEqual( - str(c.vevent.valarm.trigger), - "" - ) + self.assertEqual(str(c.vevent.valarm.trigger), "") - self.assertEqual( - str(c.vevent.dtstart.value), - "2002-10-28 14:00:00-08:00" - ) - self.assertTrue( - isinstance(c.vevent.dtstart.value, datetime.datetime) - ) - self.assertEqual( - str(c.vevent.dtend.value), - "2002-10-28 15:00:00-08:00" - ) - self.assertTrue( - isinstance(c.vevent.dtend.value, datetime.datetime) - ) - self.assertEqual( - c.vevent.dtstamp.value, - datetime.datetime(2002, 10, 28, 1, 17, 6, tzinfo=tzutc()) - ) + self.assertEqual(str(c.vevent.dtstart.value), "2002-10-28 14:00:00-08:00") + self.assertTrue(isinstance(c.vevent.dtstart.value, datetime.datetime)) + self.assertEqual(str(c.vevent.dtend.value), "2002-10-28 15:00:00-08:00") + self.assertTrue(isinstance(c.vevent.dtend.value, datetime.datetime)) + self.assertEqual(c.vevent.dtstamp.value, datetime.datetime(2002, 10, 28, 1, 17, 6, tzinfo=tzutc())) vevent = c.vevent.transformFromNative() - self.assertEqual( - str(vevent.rrule), - "" - ) + self.assertEqual(str(vevent.rrule), "") def test_bad_stream(self): """ @@ -425,23 +438,16 @@ def test_bad_line(self): self.assertRaises(ParseError, base.readOne, cal) newcal = base.readOne(cal, ignoreUnreadable=True) - self.assertEqual( - str(newcal.vevent.x_bad_underscore), - '' - ) + self.assertEqual(str(newcal.vevent.x_bad_underscore), "") def test_parseParams(self): """ Test parsing parameters """ - self.assertEqual( - base.parseParams(';ALTREP="http://www.wiz.org"'), - [['ALTREP', 'http://www.wiz.org']] - ) + self.assertEqual(base.parseParams(';ALTREP="http://www.wiz.org"'), [["ALTREP", "http://www.wiz.org"]]) self.assertEqual( base.parseParams(';ALTREP="http://www.wiz.org;;",Blah,Foo;NEXT=Nope;BAR'), - [['ALTREP', 'http://www.wiz.org;;', 'Blah', 'Foo'], - ['NEXT', 'Nope'], ['BAR']] + [["ALTREP", "http://www.wiz.org;;", "Blah", "Foo"], ["NEXT", "Nope"], ["BAR"]], ) @@ -449,6 +455,7 @@ class TestVcards(unittest.TestCase): """ Test VCards """ + @classmethod def setUpClass(cls): """ @@ -462,24 +469,17 @@ def test_vcard_creation(self): """ Test creating a vCard """ - vcard = base.newFromBehavior('vcard', '3.0') - self.assertEqual( - str(vcard), - "" - ) + vcard = base.newFromBehavior("vcard", "3.0") + self.assertEqual(str(vcard), "") def test_default_behavior(self): """ Default behavior test. """ card = self.card + self.assertEqual(base.getBehavior("note"), None) self.assertEqual( - base.getBehavior('note'), - None - ) - self.assertEqual( - str(card.note.value), - "The Mayor of the great city of Goerlitz in the great country of Germany.\nNext line." + str(card.note.value), "The Mayor of the great city of Goerlitz in the great country of Germany.\nNext line." ) def test_with_groups(self): @@ -487,25 +487,12 @@ def test_with_groups(self): vCard groups test """ card = self.card - self.assertEqual( - str(card.group), - 'home' - ) - self.assertEqual( - str(card.tel.group), - 'home' - ) - - card.group = card.tel.group = 'new' - self.assertEqual( - str(card.tel.serialize().strip()), - 'new.TEL;TYPE=fax,voice,msg:+49 3581 123456' - ) - self.assertEqual( - str(card.serialize().splitlines()[0]), - 'new.BEGIN:VCARD' - ) + self.assertEqual(str(card.group), "home") + self.assertEqual(str(card.tel.group), "home") + card.group = card.tel.group = "new" + self.assertEqual(str(card.tel.serialize().strip()), "new.TEL;TYPE=fax,voice,msg:+49 3581 123456") + self.assertEqual(str(card.serialize().splitlines()[0]), "new.BEGIN:VCARD") def test_vcard_3_parsing(self): """ @@ -514,14 +501,11 @@ def test_vcard_3_parsing(self): test_file = get_test_file("simple_3_0_test.ics") card = base.readOne(test_file) # value not rendering correctly? - #self.assertEqual( + # self.assertEqual( # card.adr.value, # "" - #) - self.assertEqual( - card.org.value, - ["University of Novosibirsk", "Department of Octopus Parthenogenesis"] - ) + # ) + self.assertEqual(card.org.value, ["University of Novosibirsk", "Department of Octopus Parthenogenesis"]) for _ in range(3): new_card = base.readOne(card.serialize()) @@ -533,65 +517,39 @@ class TestIcalendar(unittest.TestCase): """ Tests for icalendar.py """ + max_diff = None + def test_parseDTStart(self): """ Should take a content line and return a datetime object. """ self.assertEqual( - parseDtstart(textLineToContentLine("DTSTART:20060509T000000")), - datetime.datetime(2006, 5, 9, 0, 0) + parseDtstart(textLineToContentLine("DTSTART:20060509T000000")), datetime.datetime(2006, 5, 9, 0, 0) ) def test_regexes(self): """ Test regex patterns """ + self.assertEqual(re.findall(base.patterns["name"], "12foo-bar:yay"), ["12foo-bar", "yay"]) + self.assertEqual(re.findall(base.patterns["safe_char"], 'a;b"*,cd'), ["a", "b", "*", "c", "d"]) + self.assertEqual(re.findall(base.patterns["qsafe_char"], 'a;b"*,cd'), ["a", ";", "b", "*", ",", "c", "d"]) self.assertEqual( - re.findall(base.patterns['name'], '12foo-bar:yay'), - ['12foo-bar', 'yay'] - ) - self.assertEqual( - re.findall(base.patterns['safe_char'], 'a;b"*,cd'), - ['a', 'b', '*', 'c', 'd'] - ) - self.assertEqual( - re.findall(base.patterns['qsafe_char'], 'a;b"*,cd'), - ['a', ';', 'b', '*', ',', 'c', 'd'] - ) - self.assertEqual( - re.findall(base.patterns['param_value'], - '"quoted";not-quoted;start"after-illegal-quote', - re.VERBOSE), - ['"quoted"', '', 'not-quoted', '', 'start', '', - 'after-illegal-quote', ''] + re.findall(base.patterns["param_value"], '"quoted";not-quoted;start"after-illegal-quote', re.VERBOSE), + ['"quoted"', "", "not-quoted", "", "start", "", "after-illegal-quote", ""], ) match = base.line_re.match('TEST;ALTREP="http://www.wiz.org":value:;"') - self.assertEqual( - match.group('value'), - 'value:;"' - ) - self.assertEqual( - match.group('name'), - 'TEST' - ) - self.assertEqual( - match.group('params'), - ';ALTREP="http://www.wiz.org"' - ) + self.assertEqual(match.group("value"), 'value:;"') + self.assertEqual(match.group("name"), "TEST") + self.assertEqual(match.group("params"), ';ALTREP="http://www.wiz.org"') def test_stringToTextValues(self): """ Test string lists """ - self.assertEqual( - stringToTextValues(''), - [''] - ) - self.assertEqual( - stringToTextValues('abcd,efgh'), - ['abcd', 'efgh'] - ) + self.assertEqual(stringToTextValues(""), [""]) + self.assertEqual(stringToTextValues("abcd,efgh"), ["abcd", "efgh"]) def test_stringToPeriod(self): """ @@ -599,27 +557,19 @@ def test_stringToPeriod(self): """ self.assertEqual( stringToPeriod("19970101T180000Z/19970102T070000Z"), - (datetime.datetime(1997, 1, 1, 18, 0, tzinfo=tzutc()), - datetime.datetime(1997, 1, 2, 7, 0, tzinfo=tzutc())) + (datetime.datetime(1997, 1, 1, 18, 0, tzinfo=tzutc()), datetime.datetime(1997, 1, 2, 7, 0, tzinfo=tzutc())), ) self.assertEqual( stringToPeriod("19970101T180000Z/PT1H"), - (datetime.datetime(1997, 1, 1, 18, 0, tzinfo=tzutc()), - datetime.timedelta(0, 3600)) + (datetime.datetime(1997, 1, 1, 18, 0, tzinfo=tzutc()), datetime.timedelta(0, 3600)), ) def test_timedeltaToString(self): """ Test timedelta strings """ - self.assertEqual( - timedeltaToString(two_hours), - 'PT2H' - ) - self.assertEqual( - timedeltaToString(datetime.timedelta(minutes=20)), - 'PT20M' - ) + self.assertEqual(timedeltaToString(two_hours), "PT2H") + self.assertEqual(timedeltaToString(datetime.timedelta(minutes=20)), "PT20M") def test_delta_to_offset(self): """Test deltaToOffset() function.""" @@ -641,21 +591,14 @@ def test_vtimezone_creation(self): Test timezones """ tzs = dateutil.tz.tzical("test_files/timezones.ics") - pacific = icalendar.TimezoneComponent(tzs.get('US/Pacific')) - self.assertEqual( - str(pacific), - ">" - ) - santiago = icalendar.TimezoneComponent(tzs.get('Santiago')) - self.assertEqual( - str(santiago), - ">" - ) + pacific = icalendar.TimezoneComponent(tzs.get("US/Pacific")) + self.assertEqual(str(pacific), ">") + santiago = icalendar.TimezoneComponent(tzs.get("Santiago")) + self.assertEqual(str(santiago), ">") for year in range(2001, 2010): for month in (2, 9): - dt = datetime.datetime(year, month, 15, - tzinfo=tzs.get('Santiago')) - self.assertTrue(dt.replace(tzinfo=tzs.get('Santiago')), dt) + dt = datetime.datetime(year, month, 15, tzinfo=tzs.get("Santiago")) + self.assertTrue(dt.replace(tzinfo=tzs.get("Santiago")), dt) @staticmethod def test_timezone_serializing(): @@ -663,21 +606,19 @@ def test_timezone_serializing(): Serializing with timezones test """ tzs = dateutil.tz.tzical("test_files/timezones.ics") - pacific = tzs.get('US/Pacific') - cal = base.Component('VCALENDAR') + pacific = tzs.get("US/Pacific") + cal = base.Component("VCALENDAR") cal.setBehavior(icalendar.VCalendar2_0) - ev = cal.add('vevent') - ev.add('dtstart').value = datetime.datetime(2005, 10, 12, 9, - tzinfo=pacific) + ev = cal.add("vevent") + ev.add("dtstart").value = datetime.datetime(2005, 10, 12, 9, tzinfo=pacific) evruleset = rruleset() - evruleset.rrule(rrule(WEEKLY, interval=2, byweekday=[2,4], - until=datetime.datetime(2005, 12, 15, 9))) - evruleset.rrule(rrule(MONTHLY, bymonthday=[-1,-5])) + evruleset.rrule(rrule(WEEKLY, interval=2, byweekday=[2, 4], until=datetime.datetime(2005, 12, 15, 9))) + evruleset.rrule(rrule(MONTHLY, bymonthday=[-1, -5])) evruleset.exdate(datetime.datetime(2005, 10, 14, 9, tzinfo=pacific)) ev.rruleset = evruleset - ev.add('duration').value = datetime.timedelta(hours=1) + ev.add("duration").value = datetime.timedelta(hours=1) - apple = tzs.get('America/Montreal') + apple = tzs.get("America/Montreal") ev.dtstart.value = datetime.datetime(2005, 10, 12, 9, tzinfo=apple) def test_pytz_timezone_serializing(self): @@ -695,20 +636,16 @@ def unregister_tzid(tzid): if icalendar.getTzid(tzid, False): icalendar.registerTzid(tzid, None) - unregister_tzid('US/Eastern') - eastern = pytz.timezone('US/Eastern') - cal = base.Component('VCALENDAR') + unregister_tzid("US/Eastern") + eastern = pytz.timezone("US/Eastern") + cal = base.Component("VCALENDAR") cal.setBehavior(icalendar.VCalendar2_0) - ev = cal.add('vevent') - ev.add('dtstart').value = eastern.localize( - datetime.datetime(2008, 10, 12, 9)) + ev = cal.add("vevent") + ev.add("dtstart").value = eastern.localize(datetime.datetime(2008, 10, 12, 9)) serialized = cal.serialize() expected_vtimezone = get_test_file("tz_us_eastern.ics") - self.assertIn( - expected_vtimezone.replace('\r\n', '\n'), - serialized.replace('\r\n', '\n') - ) + self.assertIn(expected_vtimezone.replace("\r\n", "\n"), serialized.replace("\r\n", "\n")) # Exhaustively test all zones (just looking for no errors) for tzname in pytz.all_timezones: @@ -722,18 +659,15 @@ def test_freeBusy(self): """ test_cal = get_test_file("freebusy.ics") - vfb = base.newFromBehavior('VFREEBUSY') - vfb.add('uid').value = 'test' - vfb.add('dtstamp').value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc) - vfb.add('dtstart').value = datetime.datetime(2006, 2, 16, 1, tzinfo=utc) - vfb.add('dtend').value = vfb.dtstart.value + two_hours - vfb.add('freebusy').value = [(vfb.dtstart.value, two_hours / 2)] - vfb.add('freebusy').value = [(vfb.dtstart.value, vfb.dtend.value)] + vfb = base.newFromBehavior("VFREEBUSY") + vfb.add("uid").value = "test" + vfb.add("dtstamp").value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc) + vfb.add("dtstart").value = datetime.datetime(2006, 2, 16, 1, tzinfo=utc) + vfb.add("dtend").value = vfb.dtstart.value + two_hours + vfb.add("freebusy").value = [(vfb.dtstart.value, two_hours / 2)] + vfb.add("freebusy").value = [(vfb.dtstart.value, vfb.dtend.value)] - self.assertEqual( - vfb.serialize().replace('\r\n', '\n'), - test_cal.replace('\r\n', '\n') - ) + self.assertEqual(vfb.serialize().replace("\r\n", "\n"), test_cal.replace("\r\n", "\n")) def test_availablity(self): """ @@ -741,26 +675,23 @@ def test_availablity(self): """ test_cal = get_test_file("availablity.ics") - vcal = base.newFromBehavior('VAVAILABILITY') - vcal.add('uid').value = 'test' - vcal.add('dtstamp').value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc) - vcal.add('dtstart').value = datetime.datetime(2006, 2, 16, 0, tzinfo=utc) - vcal.add('dtend').value = datetime.datetime(2006, 2, 17, 0, tzinfo=utc) - vcal.add('busytype').value = "BUSY" + vcal = base.newFromBehavior("VAVAILABILITY") + vcal.add("uid").value = "test" + vcal.add("dtstamp").value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc) + vcal.add("dtstart").value = datetime.datetime(2006, 2, 16, 0, tzinfo=utc) + vcal.add("dtend").value = datetime.datetime(2006, 2, 17, 0, tzinfo=utc) + vcal.add("busytype").value = "BUSY" - av = base.newFromBehavior('AVAILABLE') - av.add('uid').value = 'test1' - av.add('dtstamp').value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc) - av.add('dtstart').value = datetime.datetime(2006, 2, 16, 9, tzinfo=utc) - av.add('dtend').value = datetime.datetime(2006, 2, 16, 12, tzinfo=utc) - av.add('summary').value = "Available in the morning" + av = base.newFromBehavior("AVAILABLE") + av.add("uid").value = "test1" + av.add("dtstamp").value = datetime.datetime(2006, 2, 15, 0, tzinfo=utc) + av.add("dtstart").value = datetime.datetime(2006, 2, 16, 9, tzinfo=utc) + av.add("dtend").value = datetime.datetime(2006, 2, 16, 12, tzinfo=utc) + av.add("summary").value = "Available in the morning" vcal.add(av) - self.assertEqual( - vcal.serialize().replace('\r\n', '\n'), - test_cal.replace('\r\n', '\n') - ) + self.assertEqual(vcal.serialize().replace("\r\n", "\n"), test_cal.replace("\r\n", "\n")) def test_recurrence(self): """ @@ -770,24 +701,15 @@ def test_recurrence(self): test_file = get_test_file("recurrence.ics") cal = base.readOne(test_file) dates = list(cal.vevent.getrruleset()) - self.assertEqual( - dates[0], - datetime.datetime(2006, 1, 26, 23, 0, tzinfo=tzutc()) - ) - self.assertEqual( - dates[1], - datetime.datetime(2006, 2, 23, 23, 0, tzinfo=tzutc()) - ) - self.assertEqual( - dates[-1], - datetime.datetime(2006, 12, 28, 23, 0, tzinfo=tzutc()) - ) + self.assertEqual(dates[0], datetime.datetime(2006, 1, 26, 23, 0, tzinfo=tzutc())) + self.assertEqual(dates[1], datetime.datetime(2006, 2, 23, 23, 0, tzinfo=tzutc())) + self.assertEqual(dates[-1], datetime.datetime(2006, 12, 28, 23, 0, tzinfo=tzutc())) def test_recurring_component(self): """ Test recurring events """ - vevent = RecurringComponent(name='VEVENT') + vevent = RecurringComponent(name="VEVENT") # init self.assertTrue(vevent.isNative) @@ -797,27 +719,24 @@ def test_recurring_component(self): self.assertEqual(vevent.rruleset, None) # Now add start and rule for recurring event - vevent.add('dtstart').value = datetime.datetime(2005, 1, 19, 9) - vevent.add('rrule').value =u"FREQ=WEEKLY;COUNT=2;INTERVAL=2;BYDAY=TU,TH" + vevent.add("dtstart").value = datetime.datetime(2005, 1, 19, 9) + vevent.add("rrule").value = "FREQ=WEEKLY;COUNT=2;INTERVAL=2;BYDAY=TU,TH" self.assertEqual( - list(vevent.rruleset), - [datetime.datetime(2005, 1, 20, 9, 0), datetime.datetime(2005, 2, 1, 9, 0)] + list(vevent.rruleset), [datetime.datetime(2005, 1, 20, 9, 0), datetime.datetime(2005, 2, 1, 9, 0)] ) self.assertEqual( list(vevent.getrruleset(addRDate=True)), - [datetime.datetime(2005, 1, 19, 9, 0), datetime.datetime(2005, 1, 20, 9, 0)] + [datetime.datetime(2005, 1, 19, 9, 0), datetime.datetime(2005, 1, 20, 9, 0)], ) # Also note that dateutil will expand all-day events (datetime.date values) # to datetime.datetime value with time 0 and no timezone. - vevent.dtstart.value = datetime.date(2005,3,18) + vevent.dtstart.value = datetime.date(2005, 3, 18) self.assertEqual( - list(vevent.rruleset), - [datetime.datetime(2005, 3, 29, 0, 0), datetime.datetime(2005, 3, 31, 0, 0)] + list(vevent.rruleset), [datetime.datetime(2005, 3, 29, 0, 0), datetime.datetime(2005, 3, 31, 0, 0)] ) self.assertEqual( - list(vevent.getrruleset(True)), - [datetime.datetime(2005, 3, 18, 0, 0), datetime.datetime(2005, 3, 29, 0, 0)] + list(vevent.getrruleset(True)), [datetime.datetime(2005, 3, 18, 0, 0), datetime.datetime(2005, 3, 29, 0, 0)] ) def test_recurrence_without_tz(self): @@ -848,6 +767,7 @@ class TestChangeTZ(unittest.TestCase): """ Tests for change_tz.change_tz """ + class StubCal(object): class StubEvent(object): class Node(object): @@ -871,29 +791,38 @@ def test_change_tz(self): """ # Setup - create a stub vevent list - old_tz = dateutil.tz.gettz('UTC') # 0:00 - new_tz = dateutil.tz.gettz('America/Chicago') # -5:00 + old_tz = dateutil.tz.gettz("UTC") # 0:00 + new_tz = dateutil.tz.gettz("America/Chicago") # -5:00 dates = [ - (datetime.datetime(1999, 12, 31, 23, 59, 59, 0, tzinfo=old_tz), - datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=old_tz)), - (datetime.datetime(2010, 12, 31, 23, 59, 59, 0, tzinfo=old_tz), - datetime.datetime(2011, 1, 2, 3, 0, 0, 0, tzinfo=old_tz))] + ( + datetime.datetime(1999, 12, 31, 23, 59, 59, 0, tzinfo=old_tz), + datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=old_tz), + ), + ( + datetime.datetime(2010, 12, 31, 23, 59, 59, 0, tzinfo=old_tz), + datetime.datetime(2011, 1, 2, 3, 0, 0, 0, tzinfo=old_tz), + ), + ] cal = self.StubCal(dates) # Exercise - change the timezone - change_tz(cal, new_tz, dateutil.tz.gettz('UTC')) + change_tz(cal, new_tz, dateutil.tz.gettz("UTC")) # Test - that the tzs were converted correctly expected_new_dates = [ - (datetime.datetime(1999, 12, 31, 17, 59, 59, 0, tzinfo=new_tz), - datetime.datetime(1999, 12, 31, 18, 0, 0, 0, tzinfo=new_tz)), - (datetime.datetime(2010, 12, 31, 17, 59, 59, 0, tzinfo=new_tz), - datetime.datetime(2011, 1, 1, 21, 0, 0, 0, tzinfo=new_tz))] - - for vevent, expected_datepair in zip(cal.vevent_list, - expected_new_dates): + ( + datetime.datetime(1999, 12, 31, 17, 59, 59, 0, tzinfo=new_tz), + datetime.datetime(1999, 12, 31, 18, 0, 0, 0, tzinfo=new_tz), + ), + ( + datetime.datetime(2010, 12, 31, 17, 59, 59, 0, tzinfo=new_tz), + datetime.datetime(2011, 1, 1, 21, 0, 0, 0, tzinfo=new_tz), + ), + ] + + for vevent, expected_datepair in zip(cal.vevent_list, expected_new_dates): self.assertEqual(vevent.dtstart.value, expected_datepair[0]) self.assertEqual(vevent.dtend.value, expected_datepair[1]) @@ -904,26 +833,26 @@ def test_change_tz_utc_only(self): """ # Setup - create a stub vevent list - utc_tz = dateutil.tz.gettz('UTC') # 0:00 - non_utc_tz = dateutil.tz.gettz('America/Santiago') # -4:00 - new_tz = dateutil.tz.gettz('America/Chicago') # -5:00 + utc_tz = dateutil.tz.gettz("UTC") # 0:00 + non_utc_tz = dateutil.tz.gettz("America/Santiago") # -4:00 + new_tz = dateutil.tz.gettz("America/Chicago") # -5:00 dates = [ - (datetime.datetime(1999, 12, 31, 23, 59, 59, 0, tzinfo=utc_tz), - datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=non_utc_tz))] + ( + datetime.datetime(1999, 12, 31, 23, 59, 59, 0, tzinfo=utc_tz), + datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=non_utc_tz), + ) + ] cal = self.StubCal(dates) # Exercise - change the timezone passing utc_only=True - change_tz(cal, new_tz, dateutil.tz.gettz('UTC'), utc_only=True) + change_tz(cal, new_tz, dateutil.tz.gettz("UTC"), utc_only=True) # Test - that only the utc item has changed - expected_new_dates = [ - (datetime.datetime(1999, 12, 31, 17, 59, 59, 0, tzinfo=new_tz), - dates[0][1])] + expected_new_dates = [(datetime.datetime(1999, 12, 31, 17, 59, 59, 0, tzinfo=new_tz), dates[0][1])] - for vevent, expected_datepair in zip(cal.vevent_list, - expected_new_dates): + for vevent, expected_datepair in zip(cal.vevent_list, expected_new_dates): self.assertEqual(vevent.dtstart.value, expected_datepair[0]) self.assertEqual(vevent.dtend.value, expected_datepair[1]) @@ -935,24 +864,29 @@ def test_change_tz_default(self): """ # Setup - create a stub vevent list - new_tz = dateutil.tz.gettz('America/Chicago') # -5:00 + new_tz = dateutil.tz.gettz("America/Chicago") # -5:00 dates = [ - (datetime.datetime(1999, 12, 31, 23, 59, 59, 0, tzinfo=None), - datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=None))] + ( + datetime.datetime(1999, 12, 31, 23, 59, 59, 0, tzinfo=None), + datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=None), + ) + ] cal = self.StubCal(dates) # Exercise - change the timezone - change_tz(cal, new_tz, dateutil.tz.gettz('UTC')) + change_tz(cal, new_tz, dateutil.tz.gettz("UTC")) # Test - that the tzs were converted correctly expected_new_dates = [ - (datetime.datetime(1999, 12, 31, 17, 59, 59, 0, tzinfo=new_tz), - datetime.datetime(1999, 12, 31, 18, 0, 0, 0, tzinfo=new_tz))] + ( + datetime.datetime(1999, 12, 31, 17, 59, 59, 0, tzinfo=new_tz), + datetime.datetime(1999, 12, 31, 18, 0, 0, 0, tzinfo=new_tz), + ) + ] - for vevent, expected_datepair in zip(cal.vevent_list, - expected_new_dates): + for vevent, expected_datepair in zip(cal.vevent_list, expected_new_dates): self.assertEqual(vevent.dtstart.value, expected_datepair[0]) self.assertEqual(vevent.dtend.value, expected_datepair[1]) @@ -974,5 +908,5 @@ def test_radicale_0827(self): return -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/vobject/__init__.py b/vobject/__init__.py index 416dea7..fe13acd 100644 --- a/vobject/__init__.py +++ b/vobject/__init__.py @@ -76,13 +76,13 @@ """ -from .base import newFromBehavior, readOne, readComponents from . import icalendar, vcard +from .base import newFromBehavior, readComponents, readOne def iCalendar(): - return newFromBehavior('vcalendar', '2.0') + return newFromBehavior("vcalendar", "2.0") def vCard(): - return newFromBehavior('vcard', '3.0') + return newFromBehavior("vcard", "3.0") diff --git a/vobject/base.py b/vobject/base.py index b51f308..f46ea5f 100644 --- a/vobject/base.py +++ b/vobject/base.py @@ -2,13 +2,14 @@ from __future__ import print_function -import copy import codecs +import copy import logging import re -import six import sys +import six + # ------------------------------------ Python 2/3 compatibility challenges ---- # Python 3 no longer has a basestring type, so.... try: @@ -19,24 +20,27 @@ # One more problem ... in python2 the str operator breaks on unicode # objects containing non-ascii characters try: - unicode + unicode # noqa # todo: check this def str_(s): """ Return byte string with correct encoding """ - if type(s) == unicode: - return s.encode('utf-8') + if type(s) is unicode: # noqa + return s.encode("utf-8") else: return str(s) + except NameError: + def str_(s): """ Return string """ return s -if not isinstance(b'', type('')): + +if not isinstance(b"", type("")): unicode_type = str else: unicode_type = unicode # noqa @@ -51,7 +55,7 @@ def to_unicode(value): if isinstance(value, unicode_type): return value - return value.decode('utf-8') + return value.decode("utf-8") def to_basestring(s): @@ -63,26 +67,28 @@ def to_basestring(s): if isinstance(s, bytes): return s - return s.encode('utf-8') + return s.encode("utf-8") + # ------------------------------------ Logging --------------------------------- logger = logging.getLogger(__name__) if not logging.getLogger().handlers: handler = logging.StreamHandler() - formatter = logging.Formatter('%(name)s %(levelname)s %(message)s') + formatter = logging.Formatter("%(name)s %(levelname)s %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(logging.ERROR) # Log errors DEBUG = False # Don't waste time on debug calls # ----------------------------------- Constants -------------------------------- -CR = '\r' -LF = '\n' +CR = "\r" +LF = "\n" CRLF = CR + LF -SPACE = ' ' -TAB = '\t' +SPACE = " " +TAB = "\t" SPACEORTAB = SPACE + TAB + # --------------------------------- Main classes ------------------------------- @@ -103,6 +109,7 @@ class VBase(object): Current spec: 4.0 (http://tools.ietf.org/html/rfc6350) """ + def __init__(self, group=None, *args, **kwds): super(VBase, self).__init__(*args, **kwds) self.group = group @@ -185,7 +192,7 @@ def transformToNative(self): return self.behavior.transformToNative(self) except Exception as e: # wrap errors in transformation in a ParseError - lineNumber = getattr(self, 'lineNumber', None) + lineNumber = getattr(self, "lineNumber", None) if isinstance(e, ParseError): if lineNumber is not None: @@ -215,7 +222,7 @@ def transformFromNative(self): return self.behavior.transformFromNative(self) except Exception as e: # wrap errors in transformation in a NativeError - lineNumber = getattr(self, 'lineNumber', None) + lineNumber = getattr(self, "lineNumber", None) if isinstance(e, NativeError): if lineNumber is not None: e.lineNumber = lineNumber @@ -267,7 +274,7 @@ def toVName(name, stripNum=0, upper=False): name = name.upper() if stripNum != 0: name = name[:-stripNum] - return name.replace('_', '-') + return name.replace("_", "-") class ContentLine(VBase): @@ -296,8 +303,8 @@ class ContentLine(VBase): @ivar lineNumber: An optional line number associated with the contentline. """ - def __init__(self, name, params, value, group=None, encoded=False, - isNative=False, lineNumber=None, *args, **kwds): + + def __init__(self, name, params, value, group=None, encoded=False, isNative=False, lineNumber=None, *args, **kwds): """ Take output from parseLine, convert params list to dictionary. @@ -323,27 +330,31 @@ def updateTable(x): list(map(updateTable, params)) qp = False - if 'ENCODING' in self.params: - if 'QUOTED-PRINTABLE' in self.params['ENCODING']: + if "ENCODING" in self.params: + if "QUOTED-PRINTABLE" in self.params["ENCODING"]: qp = True - self.params['ENCODING'].remove('QUOTED-PRINTABLE') - if len(self.params['ENCODING']) == 0: - del self.params['ENCODING'] - if 'QUOTED-PRINTABLE' in self.singletonparams: + self.params["ENCODING"].remove("QUOTED-PRINTABLE") + if len(self.params["ENCODING"]) == 0: + del self.params["ENCODING"] + if "QUOTED-PRINTABLE" in self.singletonparams: qp = True - self.singletonparams.remove('QUOTED-PRINTABLE') + self.singletonparams.remove("QUOTED-PRINTABLE") if qp: - if 'ENCODING' in self.params: - self.value = codecs.decode(self.value.encode("utf-8"), "quoted-printable").decode(self.params['ENCODING']) + if "ENCODING" in self.params: + self.value = codecs.decode(self.value.encode("utf-8"), "quoted-printable").decode( + self.params["ENCODING"] + ) else: - if 'CHARSET' in self.params: - self.value = codecs.decode(self.value.encode("utf-8"), "quoted-printable").decode(self.params['CHARSET'][0]) + if "CHARSET" in self.params: + self.value = codecs.decode(self.value.encode("utf-8"), "quoted-printable").decode( + self.params["CHARSET"][0] + ) else: - self.value = codecs.decode(self.value.encode("utf-8"), "quoted-printable").decode('utf-8') + self.value = codecs.decode(self.value.encode("utf-8"), "quoted-printable").decode("utf-8") @classmethod - def duplicate(clz, copyit): - newcopy = clz('', {}, '') + def duplicate(cls, copyit): + newcopy = cls("", {}, "") newcopy.copy(copyit) return newcopy @@ -372,9 +383,9 @@ def __getattr__(self, name): which are legal in IANA tokens. """ try: - if name.endswith('_param'): + if name.endswith("_param"): return self.params[toVName(name, 6, True)][0] - elif name.endswith('_paramlist'): + elif name.endswith("_paramlist"): return self.params[toVName(name, 10, True)] else: raise AttributeError(name) @@ -388,13 +399,13 @@ def __setattr__(self, name, value): Underscores, legal in python variable names, are converted to dashes, which are legal in IANA tokens. """ - if name.endswith('_param'): - if type(value) == list: + if name.endswith("_param"): + if type(value) is list: self.params[toVName(name, 6, True)] = value else: self.params[toVName(name, 6, True)] = [value] - elif name.endswith('_paramlist'): - if type(value) == list: + elif name.endswith("_paramlist"): + if type(value) is list: self.params[toVName(name, 10, True)] = value else: raise VObjectError("Parameter list set to a non-list") @@ -407,9 +418,9 @@ def __setattr__(self, name, value): def __delattr__(self, name): try: - if name.endswith('_param'): + if name.endswith("_param"): del self.params[toVName(name, 6, True)] - elif name.endswith('_paramlist'): + elif name.endswith("_paramlist"): del self.params[toVName(name, 10, True)] else: object.__delattr__(self, name) @@ -429,22 +440,22 @@ def valueRepr(self): def __str__(self): try: return "<{0}{1}{2}>".format(self.name, self.params, self.valueRepr()) - except UnicodeEncodeError as e: - return "<{0}{1}{2}>".format(self.name, self.params, self.valueRepr().encode('utf-8')) + except UnicodeEncodeError: + return "<{0}{1}{2}>".format(self.name, self.params, self.valueRepr().encode("utf-8")) def __repr__(self): return self.__str__() def __unicode__(self): - return u"<{0}{1}{2}>".format(self.name, self.params, self.valueRepr()) + return "<{0}{1}{2}>".format(self.name, self.params, self.valueRepr()) def prettyPrint(self, level=0, tabwidth=3): - pre = ' ' * level * tabwidth + pre = " " * level * tabwidth print(pre, self.name + ":", self.valueRepr()) if self.params: - print(pre, "params for ", self.name + ':') + print(pre, "params for ", self.name + ":") for k in self.params.keys(): - print(pre + ' ' * tabwidth, k, self.params[k]) + print(pre + " " * tabwidth, k, self.params[k]) class Component(VBase): @@ -465,6 +476,7 @@ class Component(VBase): A boolean flag determining whether BEGIN: and END: lines should be serialized. """ + def __init__(self, name=None, *args, **kwds): super(Component, self).__init__(*args, **kwds) self.contents = {} @@ -472,7 +484,7 @@ def __init__(self, name=None, *args, **kwds): self.name = name.upper() self.useBegin = True else: - self.name = '' + self.name = "" self.useBegin = False self.autoBehavior() @@ -507,8 +519,7 @@ def setProfile(self, name): if self.name or self.useBegin: if self.name == name: return - raise VObjectError("This component already has a PROFILE or " - "uses BEGIN.") + raise VObjectError("This component already has a PROFILE or " "uses BEGIN.") self.name = name.upper() def __getattr__(self, name): @@ -520,17 +531,17 @@ def __getattr__(self, name): """ # if the object is being re-created by pickle, self.contents may not # be set, don't get into an infinite loop over the issue - if name == 'contents': + if name == "contents": return object.__getattribute__(self, name) try: - if name.endswith('_list'): + if name.endswith("_list"): return self.contents[toVName(name, 5)] else: return self.contents[toVName(name)][0] except KeyError: raise AttributeError(name) - normal_attributes = ['contents', 'name', 'behavior', 'parentBehavior', 'group'] + normal_attributes = ["contents", "name", "behavior", "parentBehavior", "group"] def __setattr__(self, name, value): """ @@ -540,11 +551,11 @@ def __setattr__(self, name, value): which are legal in IANA tokens. """ if name not in self.normal_attributes and name.lower() == name: - if type(value) == list: - if name.endswith('_list'): + if type(value) is list: + if name.endswith("_list"): name = name[:-5] self.contents[toVName(name)] = value - elif name.endswith('_list'): + elif name.endswith("_list"): raise VObjectError("Component list set to a non-list") else: self.contents[toVName(name)] = [value] @@ -558,7 +569,7 @@ def __setattr__(self, name, value): def __delattr__(self, name): try: if name not in self.normal_attributes and name.lower() == name: - if name.endswith('_list'): + if name.endswith("_list"): del self.contents[toVName(name, 5)] else: del self.contents[toVName(name)] @@ -599,12 +610,12 @@ def add(self, objOrName, group=None): if behavior.isComponent: obj = Component(name) else: - obj = ContentLine(name, [], '', group) + obj = ContentLine(name, [], "", group) obj.parentBehavior = self.behavior obj.behavior = behavior obj = obj.transformToNative() except (KeyError, AttributeError): - obj = ContentLine(objOrName, [], '', group) + obj = ContentLine(objOrName, [], "", group) if obj.behavior is None and self.behavior is not None: if isinstance(obj, ContentLine): obj.behavior = self.behavior.defaultBehavior @@ -689,13 +700,13 @@ def __str__(self): if self.name: return "<{0}| {1}>".format(self.name, self.getSortedChildren()) else: - return u'<*unnamed*| {0}>'.format(self.getSortedChildren()) + return "<*unnamed*| {0}>".format(self.getSortedChildren()) def __repr__(self): return self.__str__() def prettyPrint(self, level=0, tabwidth=3): - pre = ' ' * level * tabwidth + pre = " " * level * tabwidth print(pre, self.name) if isinstance(self, Component): for line in self.getChildren(): @@ -709,7 +720,7 @@ def __init__(self, msg, lineNumber=None): self.lineNumber = lineNumber def __str__(self): - if hasattr(self, 'lineNumber'): + if hasattr(self, "lineNumber"): return "At line {0!s}: {1!s}".format(self.lineNumber, self.msg) else: return repr(self.msg) @@ -729,38 +740,42 @@ class NativeError(VObjectError): # --------- Parsing functions and parseLine regular expressions ---------------- -patterns = {} # Note that underscore is not legal for names, it's included because # Lotus Notes uses it -patterns['name'] = '[a-zA-Z0-9_-]+' # 1*(ALPHA / DIGIT / "-") -patterns['safe_char'] = '[^";:,]' -patterns['qsafe_char'] = '[^"]' - +patterns = {"name": "[a-zA-Z0-9_-]+", "safe_char": '[^";:,]', "qsafe_char": '[^"]'} # the combined Python string replacement and regex syntax is a little confusing; # remember that {foobar} is replaced with patterns['foobar'], so for instance # param_value is any number of safe_chars or any number of qsaf_chars surrounded # by double quotes. -patterns['param_value'] = ' "{qsafe_char!s} * " | {safe_char!s} * '.format(**patterns) - +patterns["param_value"] = ' "{qsafe_char!s} * " | {safe_char!s} * '.format(**patterns) # get a tuple of two elements, one will be empty, the other will have the value -patterns['param_value_grouped'] = """ +patterns["param_value_grouped"] = ( + """ " ( {qsafe_char!s} * )" | ( {safe_char!s} + ) -""".format(**patterns) +""".format( + **patterns + ) +) # get a parameter and its values, without any saved groups -patterns['param'] = r""" +patterns["param"] = ( + r""" ; (?: {name!s} ) # parameter name (?: (?: = (?: {param_value!s} ) )? # 0 or more parameter values, multiple (?: , (?: {param_value!s} ) )* # parameters are comma separated )* -""".format(**patterns) +""".format( + **patterns + ) +) # get a parameter, saving groups for name and value (value still needs parsing) -patterns['params_grouped'] = r""" +patterns["params_grouped"] = ( + r""" ; ( {name!s} ) (?: = @@ -769,21 +784,28 @@ class NativeError(VObjectError): (?: , (?: {param_value!s} ) )* # parameters are comma separated ) )? -""".format(**patterns) +""".format( + **patterns + ) +) # get a full content line, break it up into group, name, parameters, and value -patterns['line'] = r""" +patterns["line"] = ( + r""" ^ ((?P {name!s})\.)?(?P {name!s}) # name group (?P ;?(?: {param!s} )* ) # params group (may be empty) : (?P .* )$ # value group -""".format(**patterns) +""".format( + **patterns + ) +) ' "%(qsafe_char)s*" | %(safe_char)s* ' # what is this line?? - never assigned? -param_values_re = re.compile(patterns['param_value_grouped'], re.VERBOSE) -params_re = re.compile(patterns['params_grouped'], re.VERBOSE) -line_re = re.compile(patterns['line'], re.DOTALL | re.VERBOSE) -begin_re = re.compile('BEGIN', re.IGNORECASE) +param_values_re = re.compile(patterns["param_value_grouped"], re.VERBOSE) +params_re = re.compile(patterns["params_grouped"], re.VERBOSE) +line_re = re.compile(patterns["line"], re.DOTALL | re.VERBOSE) +begin_re = re.compile("BEGIN", re.IGNORECASE) def parseParams(string): @@ -796,7 +818,7 @@ def parseParams(string): paramList = [tup[0]] # tup looks like (name, valuesString) for pair in param_values_re.findall(tup[1]): # pair looks like ('', value) or (value, '') - if pair[0] != '': + if pair[0] != "": paramList.append(pair[0]) else: paramList.append(pair[1]) @@ -812,25 +834,33 @@ def parseLine(line, lineNumber=None): if match is None: raise ParseError("Failed to parse line: {0!s}".format(line), lineNumber) # Underscores are replaced with dash to work around Lotus Notes - return (match.group('name').replace('_', '-'), - parseParams(match.group('params')), - match.group('value'), match.group('group')) + return ( + match.group("name").replace("_", "-"), + parseParams(match.group("params")), + match.group("value"), + match.group("group"), + ) + # logical line regular expressions -patterns['lineend'] = r'(?:\r\n|\r|\n|$)' -patterns['wrap'] = r'{lineend!s} [\t ]'.format(**patterns) -patterns['logicallines'] = r""" +patterns["lineend"] = r"(?:\r\n|\r|\n|$)" +patterns["wrap"] = r"{lineend!s} [\t ]".format(**patterns) +patterns["logicallines"] = ( + r""" ( (?: [^\r\n] | {wrap!s} )* {lineend!s} ) -""".format(**patterns) +""".format( + **patterns + ) +) -patterns['wraporend'] = r'({wrap!s} | {lineend!s} )'.format(**patterns) +patterns["wraporend"] = r"({wrap!s} | {lineend!s} )".format(**patterns) -wrap_re = re.compile(patterns['wraporend'], re.VERBOSE) -logical_lines_re = re.compile(patterns['logicallines'], re.VERBOSE) +wrap_re = re.compile(patterns["wraporend"], re.VERBOSE) +logical_lines_re = re.compile(patterns["logicallines"], re.VERBOSE) testLines = """ Line 0 text @@ -870,8 +900,8 @@ def getLogicalLines(fp, allowQP=True): lineNumber = 1 for match in logical_lines_re.finditer(val): - line, n = wrap_re.subn('', match.group()) - if line != '': + line, n = wrap_re.subn("", match.group()) + if line != "": yield line, lineNumber lineNumber += n @@ -883,12 +913,12 @@ def getLogicalLines(fp, allowQP=True): lineStartNumber = 0 while True: line = fp.readline() - if line == '': + if line == "": break else: line = line.rstrip(CRLF) lineNumber += 1 - if line.rstrip() == '': + if line.rstrip() == "": if logicalLine.tell() > 0: yield logicalLine.getvalue(), lineStartNumber lineStartNumber = lineNumber @@ -897,7 +927,7 @@ def getLogicalLines(fp, allowQP=True): continue if quotedPrintable and allowQP: - logicalLine.write('\n') + logicalLine.write("\n") logicalLine.write(line) quotedPrintable = False elif line[0] in SPACEORTAB: @@ -914,7 +944,7 @@ def getLogicalLines(fp, allowQP=True): # vCard 2.1 allows parameters to be encoded without a parameter name # False positives are unlikely, but possible. val = logicalLine.getvalue() - if val[-1] == '=' and val.lower().find('quoted-printable') >= 0: + if val[-1] == "=" and val.lower().find("quoted-printable") >= 0: quotedPrintable = True if logicalLine.tell() > 0: @@ -922,8 +952,7 @@ def getLogicalLines(fp, allowQP=True): def textLineToContentLine(text, n=None): - return ContentLine(*parseLine(text, n), **{'encoded': True, - 'lineNumber': n}) + return ContentLine(*parseLine(text, n), **{"encoded": True, "lineNumber": n}) def dquoteEscape(param): @@ -932,7 +961,7 @@ def dquoteEscape(param): """ if param.find('"') >= 0: raise VObjectError("Double quotes aren't allowed in parameter values.") - for char in ',;:': + for char in ",;:": if param.find(char) >= 0: return '"' + param + '"' return param @@ -948,7 +977,7 @@ def foldOneLine(outbuf, input, lineLength=75): if len(input) < lineLength: # Optimize for unfolded line case try: - outbuf.write(bytes(input, 'UTF-8')) + outbuf.write(bytes(input, "UTF-8")) except Exception: # fall back on py2 syntax outbuf.write(input) @@ -965,7 +994,7 @@ def foldOneLine(outbuf, input, lineLength=75): size = len(to_basestring(s)) # calculate it's size in bytes if counter + size > lineLength: try: - outbuf.write(bytes("\r\n ", 'UTF-8')) + outbuf.write(bytes("\r\n ", "UTF-8")) except Exception: # fall back on py2 syntax outbuf.write("\r\n ") @@ -976,13 +1005,13 @@ def foldOneLine(outbuf, input, lineLength=75): outbuf.write(to_unicode(s)) else: # fall back on py2 syntax - outbuf.write(s.encode('utf-8')) + outbuf.write(s.encode("utf-8")) written += size counter += size start += 1 try: - outbuf.write(bytes("\r\n", 'UTF-8')) + outbuf.write(bytes("\r\n", "UTF-8")) except Exception: # fall back on py2 syntax outbuf.write("\r\n") @@ -996,18 +1025,16 @@ def defaultSerialize(obj, buf, lineLength): if isinstance(obj, Component): if obj.group is None: - groupString = '' + groupString = "" else: - groupString = obj.group + '.' + groupString = obj.group + "." if obj.useBegin: - foldOneLine(outbuf, "{0}BEGIN:{1}".format(groupString, obj.name), - lineLength) + foldOneLine(outbuf, "{0}BEGIN:{1}".format(groupString, obj.name), lineLength) for child in obj.getSortedChildren(): # validate is recursive, we only need to validate once child.serialize(outbuf, lineLength, validate=False) if obj.useBegin: - foldOneLine(outbuf, "{0}END:{1}".format(groupString, obj.name), - lineLength) + foldOneLine(outbuf, "{0}END:{1}".format(groupString, obj.name), lineLength) elif isinstance(obj, ContentLine): startedEncoded = obj.encoded @@ -1017,19 +1044,19 @@ def defaultSerialize(obj, buf, lineLength): s = six.StringIO() if obj.group is not None: - s.write(obj.group + '.') + s.write(obj.group + ".") s.write(str_(obj.name.upper())) keys = sorted(obj.params.keys()) for key in keys: - paramstr = ','.join(dquoteEscape(p) for p in obj.params[key]) + paramstr = ",".join(dquoteEscape(p) for p in obj.params[key]) try: s.write(";{0}={1}".format(key, paramstr)) except (UnicodeDecodeError, UnicodeEncodeError): - s.write(";{0}={1}".format(key, paramstr.encode('utf-8'))) + s.write(";{0}={1}".format(key, paramstr.encode("utf-8"))) try: s.write(":{0}".format(obj.value)) except (UnicodeDecodeError, UnicodeEncodeError): - s.write(":{0}".format(obj.value.encode('utf-8'))) + s.write(":{0}".format(obj.value.encode("utf-8"))) if obj.behavior and not startedEncoded: obj.behavior.decode(obj) foldOneLine(outbuf, s.getvalue(), lineLength) @@ -1072,8 +1099,7 @@ def pop(self): return self.stack.pop() -def readComponents(streamOrString, validate=False, transform=True, - ignoreUnreadable=False, allowQP=False): +def readComponents(streamOrString, validate=False, transform=True, ignoreUnreadable=False, allowQP=False): """ Generate one Component at a time from a stream. """ @@ -1095,7 +1121,7 @@ def readComponents(streamOrString, validate=False, transform=True, msg = "Skipped line {lineNumber}, message: {msg}" else: msg = "Skipped a line, message: {msg}" - logger.error(msg.format(**{'lineNumber': e.lineNumber, 'msg': str(e)})) + logger.error(msg.format(**{"lineNumber": e.lineNumber, "msg": str(e)})) continue else: vline = textLineToContentLine(line, n) @@ -1138,8 +1164,7 @@ def readComponents(streamOrString, validate=False, transform=True, if stack.topName() is None: logger.warning("Top level component was never named") elif stack.top().useBegin: - raise ParseError("Component {0!s} was never closed".format( - (stack.topName())), n) + raise ParseError("Component {0!s} was never closed".format((stack.topName())), n) yield stack.pop() except ParseError as e: @@ -1147,13 +1172,11 @@ def readComponents(streamOrString, validate=False, transform=True, raise -def readOne(stream, validate=False, transform=True, ignoreUnreadable=False, - allowQP=False): +def readOne(stream, validate=False, transform=True, ignoreUnreadable=False, allowQP=False): """ Return the first component from stream. """ - return next(readComponents(stream, validate, transform, ignoreUnreadable, - allowQP)) + return next(readComponents(stream, validate, transform, ignoreUnreadable, allowQP)) # --------------------------- version registry --------------------------------- @@ -1208,7 +1231,7 @@ def newFromBehavior(name, id=None): if behavior.isComponent: obj = Component(name) else: - obj = ContentLine(name, [], '') + obj = ContentLine(name, [], "") obj.behavior = behavior obj.isNative = False return obj diff --git a/vobject/behavior.py b/vobject/behavior.py index a1a292e..53739ff 100644 --- a/vobject/behavior.py +++ b/vobject/behavior.py @@ -1,7 +1,7 @@ from . import base -#------------------------ Abstract class for behavior -------------------------- +# ------------------------ Abstract class for behavior -------------------------- class Behavior(object): """ Behavior (validation, encoding, and transformations) for vobjects. @@ -43,9 +43,10 @@ class Behavior(object): @cvar allowGroup: Whether or not vCard style group prefixes are allowed. """ - name = '' - description = '' - versionString = '' + + name = "" + description = "" + versionString = "" knownChildren = {} quotedPrintable = False defaultBehavior = None @@ -90,7 +91,7 @@ def validate(cls, obj, raiseException=False, complainUnrecognized=False): if count.get(key, 0) < val[0]: if raiseException: m = "{0} components must contain at least {1} {2}" - raise base.ValidateError(m .format(cls.name, val[0], key)) + raise base.ValidateError(m.format(cls.name, val[0], key)) return False if val[1] and count.get(key, 0) > val[1]: if raiseException: @@ -105,6 +106,8 @@ def validate(cls, obj, raiseException=False, complainUnrecognized=False): @classmethod def lineValidate(cls, line, raiseException, complainUnrecognized): """Examine a line's parameters and values, return True if valid.""" + # todo: remove used param line, raiseException, complainUnrecognized + print(line, raiseException, complainUnrecognized) return True @classmethod diff --git a/vobject/change_tz.py b/vobject/change_tz.py index 9a3bb21..6f8c3a7 100644 --- a/vobject/change_tz.py +++ b/vobject/change_tz.py @@ -1,11 +1,12 @@ """Translate an ics file's events to a different timezone.""" from optparse import OptionParser -from vobject import icalendar, base + +from vobject import base, icalendar try: import PyICU -except: +except ImportError: PyICU = None from datetime import datetime @@ -25,14 +26,13 @@ def change_tz(cal, new_timezone, default, utc_only=False, utc_tz=icalendar.utc): utc_only=True """ - for vevent in getattr(cal, 'vevent_list', []): - start = getattr(vevent, 'dtstart', None) - end = getattr(vevent, 'dtend', None) + for vevent in getattr(cal, "vevent_list", []): + start = getattr(vevent, "dtstart", None) + end = getattr(vevent, "dtend", None) for node in (start, end): if node: dt = node.value - if (isinstance(dt, datetime) and - (not utc_only or dt.tzinfo == utc_tz)): + if isinstance(dt, datetime) and (not utc_only or dt.tzinfo == utc_tz): if dt.tzinfo is None: dt = dt.replace(tzinfo=default) node.value = dt.astimezone(new_timezone) @@ -61,10 +61,10 @@ def main(): cal = base.readOne(open(ics_file)) change_tz(cal, timezone, PyICU.ICUtzinfo.default, utc_only) - out_name = ics_file + '.converted' + out_name = ics_file + ".converted" print("... Writing {0!s}".format(out_name)) - with open(out_name, 'wb') as out: + with open(out_name, "wb") as out: cal.serialize(out) print("Done") @@ -80,10 +80,10 @@ def get_options(): parser = OptionParser(usage=usage, version=version) parser.set_description("change_tz will convert the timezones in an ics file. ") - parser.add_option("-u", "--only-utc", dest="utc", action="store_true", - default=False, help="Only change UTC events.") - parser.add_option("-l", "--list", dest="list", action="store_true", - default=False, help="List available timezones") + parser.add_option( + "-u", "--only-utc", dest="utc", action="store_true", default=False, help="Only change UTC events." + ) + parser.add_option("-l", "--list", dest="list", action="store_true", default=False, help="List available timezones") (cmdline_options, args) = parser.parse_args() if not args and not cmdline_options.list: @@ -94,6 +94,7 @@ def get_options(): return cmdline_options, args + if __name__ == "__main__": try: main() diff --git a/vobject/hcalendar.py b/vobject/hcalendar.py index e680d90..88abc11 100644 --- a/vobject/hcalendar.py +++ b/vobject/hcalendar.py @@ -12,7 +12,7 @@ DTSTART:20051005 DTEND:20051008 SUMMARY:Web 2.0 Conference -LOCATION:Argent Hotel\, San Francisco\, CA +LOCATION:Argent Hotel, San Francisco, CA END:VEVENT END:VCALENDAR @@ -28,16 +28,16 @@ """ -import six - from datetime import date, datetime, timedelta +import six + from .base import CRLF, registerBehavior from .icalendar import VCalendar2_0 class HCalendar(VCalendar2_0): - name = 'HCALENDAR' + name = "HCALENDAR" @classmethod def serialize(cls, obj, buf=None, lineLength=None, validate=True): @@ -50,7 +50,7 @@ def serialize(cls, obj, buf=None, lineLength=None, validate=True): tabwidth = 3 def indent(): - return ' ' * level * tabwidth + return " " * level * tabwidth def out(s): outbuf.write(indent()) @@ -72,24 +72,26 @@ def out(s): # SUMMARY summary = event.getChildValue("summary") if summary: - out('' + summary + ':' + CRLF) + out('' + summary + ":" + CRLF) # DTSTART dtstart = event.getChildValue("dtstart") if dtstart: - if type(dtstart) == date: + if type(dtstart) is date: timeformat = "%A, %B %e" machine = "%Y%m%d" - elif type(dtstart) == datetime: + elif type(dtstart) is datetime: timeformat = "%A, %B %e, %H:%M" machine = "%Y%m%dT%H%M%S%z" - #TODO: Handle non-datetime formats? - #TODO: Spec says we should handle when dtstart isn't included + # TODO: Handle non-datetime formats? + # TODO: Spec says we should handle when dtstart isn't included - out('{1!s}\r\n' - .format(dtstart.strftime(machine), - dtstart.strftime(timeformat))) + out( + '{1!s}\r\n'.format( + dtstart.strftime(machine), dtstart.strftime(timeformat) + ) + ) # DTEND dtend = event.getChildValue("dtend") @@ -97,34 +99,37 @@ def out(s): duration = event.getChildValue("duration") if duration: dtend = duration + dtstart - # TODO: If lacking dtend & duration? + # TODO: If lacking dtend & duration? if dtend: human = dtend # TODO: Human readable part could be smarter, excluding repeated data - if type(dtend) == date: + if type(dtend) is date: human = dtend - timedelta(days=1) - out('- {1!s}\r\n' - .format(dtend.strftime(machine), - human.strftime(timeformat))) + out( + '- {1!s}\r\n'.format( + dtend.strftime(machine), human.strftime(timeformat) + ) + ) # LOCATION location = event.getChildValue("location") if location: - out('at ' + location + '' + CRLF) + out('at ' + location + "" + CRLF) description = event.getChildValue("description") if description: - out('
' + description + '
' + CRLF) + out('
' + description + "
" + CRLF) if url: level -= 1 - out('
' + CRLF) + out("" + CRLF) level -= 1 - out('' + CRLF) # close vevent + out("" + CRLF) # close vevent return buf or outbuf.getvalue() + registerBehavior(HCalendar) diff --git a/vobject/icalendar.py b/vobject/icalendar.py index 55e63fd..0ca21f8 100644 --- a/vobject/icalendar.py +++ b/vobject/icalendar.py @@ -2,52 +2,60 @@ from __future__ import print_function +import base64 import datetime import logging import random # for generating a UID import socket import string -import base64 +from functools import partial -from dateutil import rrule, tz import six +from dateutil import rrule, tz try: import pytz except ImportError: + class Pytz: """fake pytz module (pytz is not required)""" class AmbiguousTimeError(Exception): """pytz error for ambiguous times - during transition daylight->standard""" + during transition daylight->standard""" class NonExistentTimeError(Exception): """pytz error for non-existent times - during transition standard->daylight""" + during transition standard->daylight""" pytz = Pytz # keeps quantifiedcode happy from . import behavior -from .base import (VObjectError, NativeError, ValidateError, ParseError, - Component, ContentLine, logger, registerBehavior, - backslashEscape, foldOneLine) - +from .base import ( + Component, + ContentLine, + NativeError, + ParseError, + ValidateError, + VObjectError, + backslashEscape, + foldOneLine, + logger, + registerBehavior, +) # ------------------------------- Constants ------------------------------------ DATENAMES = ("rdate", "exdate") RULENAMES = ("exrule", "rrule") DATESANDRULES = ("exrule", "rrule", "rdate", "exdate") -PRODID = u"-//PYVOBJECT//NONSGML Version 1//EN" +PRODID = "-//PYVOBJECT//NONSGML Version 1//EN" WEEKDAYS = "MO", "TU", "WE", "TH", "FR", "SA", "SU" -FREQUENCIES = ('YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', - 'SECONDLY') +FREQUENCIES = ("YEARLY", "MONTHLY", "WEEKLY", "DAILY", "HOURLY", "MINUTELY", "SECONDLY") zeroDelta = datetime.timedelta(0) twoHours = datetime.timedelta(hours=2) - # ---------------------------- TZID registry ----------------------------------- __tzidMap = {} @@ -57,7 +65,7 @@ def toUnicode(s): Take a string or unicode, turn it into unicode, decoding as utf-8 """ if isinstance(s, six.binary_type): - s = s.decode('utf-8') + s = s.decode("utf-8") return s @@ -75,7 +83,8 @@ def getTzid(tzid, smart=True): tz = __tzidMap.get(toUnicode(tzid), None) if smart and tzid and not tz: try: - from pytz import timezone, UnknownTimeZoneError + from pytz import UnknownTimeZoneError, timezone + try: tz = timezone(tzid) registerTzid(toUnicode(tzid), tz) @@ -85,6 +94,7 @@ def getTzid(tzid, smart=True): logging.error(e) return tz + utc = tz.tzutc() registerTzid("UTC", utc) @@ -105,6 +115,7 @@ class TimezoneComponent(Component): @ivar tzid: The string used to refer to this timezone. """ + def __init__(self, tzinfo=None, *args, **kwds): """ Accept an existing Component or a tzinfo class. @@ -116,24 +127,23 @@ def __init__(self, tzinfo=None, *args, **kwds): self.behavior = VTimezone if tzinfo is not None: self.tzinfo = tzinfo - if not hasattr(self, 'name') or self.name == '': - self.name = 'VTIMEZONE' + if not hasattr(self, "name") or self.name == "": + self.name = "VTIMEZONE" self.useBegin = True @classmethod - def registerTzinfo(obj, tzinfo): + def registerTzinfo(cls, tzinfo): """ Register tzinfo if it's not already registered, return its tzid. """ - tzid = obj.pickTzid(tzinfo) + tzid = cls.pickTzid(tzinfo) if tzid and not getTzid(tzid, False): registerTzid(tzid, tzinfo) return tzid def gettzinfo(self): # workaround for dateutil failing to parse some experimental properties - good_lines = ('rdate', 'rrule', 'dtstart', 'tzname', 'tzoffsetfrom', - 'tzoffsetto', 'tzid') + good_lines = ("rdate", "rrule", "dtstart", "tzname", "tzoffsetfrom", "tzoffsetto", "tzid") # serialize encodes as utf-8, cStringIO will leave utf-8 alone buffer = six.StringIO() # allow empty VTIMEZONEs @@ -142,13 +152,14 @@ def gettzinfo(self): def customSerialize(obj): if isinstance(obj, Component): - foldOneLine(buffer, u"BEGIN:" + obj.name) + foldOneLine(buffer, "BEGIN:" + obj.name) for child in obj.lines(): if child.name.lower() in good_lines: child.serialize(buffer, 75, validate=False) for comp in obj.components(): customSerialize(comp) - foldOneLine(buffer, u"END:" + obj.name) + foldOneLine(buffer, "END:" + obj.name) + customSerialize(self) buffer.seek(0) # tzical wants to read a stream return tz.tzical(buffer).get() @@ -168,6 +179,7 @@ def settzinfo(self, tzinfo, start=2000, end=2030): - tzinfo classes dst method always treats times that could be in either offset as being in the later regime """ + def fromLastWeek(dt): """ How many weeks from the end of the month dt is, starting from 1. @@ -181,45 +193,47 @@ def fromLastWeek(dt): return n # lists of dictionaries defining rules which are no longer in effect - completed = {'daylight': [], 'standard': []} + completed = {"daylight": [], "standard": []} # dictionary defining rules which are currently in effect - working = {'daylight': None, 'standard': None} + working = {"daylight": None, "standard": None} # rule may be based on nth week of the month or the nth from the last for year in range(start, end + 1): newyear = datetime.datetime(year, 1, 1) - for transitionTo in 'daylight', 'standard': + for transitionTo in "daylight", "standard": transition = getTransition(transitionTo, year, tzinfo) oldrule = working[transitionTo] if transition == newyear: # transitionTo is in effect for the whole year - rule = {'end' : None, - 'start' : newyear, - 'month' : 1, - 'weekday' : None, - 'hour' : None, - 'plus' : None, - 'minus' : None, - 'name' : tzinfo.tzname(newyear), - 'offset' : tzinfo.utcoffset(newyear), - 'offsetfrom' : tzinfo.utcoffset(newyear)} + rule = { + "end": None, + "start": newyear, + "month": 1, + "weekday": None, + "hour": None, + "plus": None, + "minus": None, + "name": tzinfo.tzname(newyear), + "offset": tzinfo.utcoffset(newyear), + "offsetfrom": tzinfo.utcoffset(newyear), + } if oldrule is None: # transitionTo was not yet in effect working[transitionTo] = rule else: # transitionTo was already in effect - if (oldrule['offset'] != tzinfo.utcoffset(newyear)): + if oldrule["offset"] != tzinfo.utcoffset(newyear): # old rule was different, it shouldn't continue - oldrule['end'] = year - 1 + oldrule["end"] = year - 1 completed[transitionTo].append(oldrule) working[transitionTo] = rule elif transition is None: # transitionTo is not in effect if oldrule is not None: # transitionTo used to be in effect - oldrule['end'] = year - 1 + oldrule["end"] = year - 1 completed[transitionTo].append(oldrule) working[transitionTo] = None else: @@ -230,43 +244,44 @@ def fromLastWeek(dt): offset = tzinfo.utcoffset(transition) except (pytz.AmbiguousTimeError, pytz.NonExistentTimeError): # guaranteed that tzinfo is a pytz timezone - is_dst = (transitionTo == "daylight") + is_dst = transitionTo == "daylight" old_offset = tzinfo.utcoffset(transition - twoHours, is_dst=is_dst) name = tzinfo.tzname(transition, is_dst=is_dst) offset = tzinfo.utcoffset(transition, is_dst=is_dst) - rule = {'end' : None, # None, or an integer year - 'start' : transition, # the datetime of transition - 'month' : transition.month, - 'weekday' : transition.weekday(), - 'hour' : transition.hour, - 'name' : name, - 'plus' : int( - (transition.day - 1)/ 7 + 1), # nth week of the month - 'minus' : fromLastWeek(transition), # nth from last week - 'offset' : offset, - 'offsetfrom' : old_offset} + rule = { + "end": None, # None, or an integer year + "start": transition, # the datetime of transition + "month": transition.month, + "weekday": transition.weekday(), + "hour": transition.hour, + "name": name, + "plus": int((transition.day - 1) / 7 + 1), # nth week of the month + "minus": fromLastWeek(transition), # nth from last week + "offset": offset, + "offsetfrom": old_offset, + } if oldrule is None: working[transitionTo] = rule else: - plusMatch = rule['plus'] == oldrule['plus'] - minusMatch = rule['minus'] == oldrule['minus'] + plusMatch = rule["plus"] == oldrule["plus"] + minusMatch = rule["minus"] == oldrule["minus"] truth = plusMatch or minusMatch - for key in 'month', 'weekday', 'hour', 'offset': + for key in "month", "weekday", "hour", "offset": truth = truth and rule[key] == oldrule[key] if truth: # the old rule is still true, limit to plus or minus if not plusMatch: - oldrule['plus'] = None + oldrule["plus"] = None if not minusMatch: - oldrule['minus'] = None + oldrule["minus"] = None else: # the new rule did not match the old - oldrule['end'] = year - 1 + oldrule["end"] = year - 1 completed[transitionTo].append(oldrule) working[transitionTo] = rule - for transitionTo in 'daylight', 'standard': + for transitionTo in "daylight", "standard": if working[transitionTo] is not None: completed[transitionTo].append(working[transitionTo]) @@ -274,56 +289,55 @@ def fromLastWeek(dt): self.daylight = [] self.standard = [] - self.add('tzid').value = self.pickTzid(tzinfo, True) + self.add("tzid").value = self.pickTzid(tzinfo, True) # old = None # unused? - for transitionTo in 'daylight', 'standard': + for transitionTo in "daylight", "standard": for rule in completed[transitionTo]: comp = self.add(transitionTo) - dtstart = comp.add('dtstart') - dtstart.value = rule['start'] - if rule['name'] is not None: - comp.add('tzname').value = rule['name'] - line = comp.add('tzoffsetto') - line.value = deltaToOffset(rule['offset']) - line = comp.add('tzoffsetfrom') - line.value = deltaToOffset(rule['offsetfrom']) - - if rule['plus'] is not None: - num = rule['plus'] - elif rule['minus'] is not None: - num = -1 * rule['minus'] + dtstart = comp.add("dtstart") + dtstart.value = rule["start"] + if rule["name"] is not None: + comp.add("tzname").value = rule["name"] + line = comp.add("tzoffsetto") + line.value = deltaToOffset(rule["offset"]) + line = comp.add("tzoffsetfrom") + line.value = deltaToOffset(rule["offsetfrom"]) + + if rule["plus"] is not None: + num = rule["plus"] + elif rule["minus"] is not None: + num = -1 * rule["minus"] else: num = None if num is not None: - dayString = ";BYDAY=" + str(num) + WEEKDAYS[rule['weekday']] + dayString = ";BYDAY=" + str(num) + WEEKDAYS[rule["weekday"]] else: dayString = "" - if rule['end'] is not None: - if rule['hour'] is None: + if rule["end"] is not None: + if rule["hour"] is None: # all year offset, with no rule - endDate = datetime.datetime(rule['end'], 1, 1) + endDate = datetime.datetime(rule["end"], 1, 1) else: - weekday = rrule.weekday(rule['weekday'], num) - du_rule = rrule.rrule(rrule.YEARLY, - bymonth=rule['month'], byweekday=weekday, - dtstart=datetime.datetime( - rule['end'], 1, 1, rule['hour'] - ) + weekday = rrule.weekday(rule["weekday"], num) + du_rule = rrule.rrule( + rrule.YEARLY, + bymonth=rule["month"], + byweekday=weekday, + dtstart=datetime.datetime(rule["end"], 1, 1, rule["hour"]), ) endDate = du_rule[0] - endDate = endDate.replace(tzinfo=utc) - rule['offsetfrom'] + endDate = endDate.replace(tzinfo=utc) - rule["offsetfrom"] endString = ";UNTIL=" + dateTimeToString(endDate) else: - endString = '' - new_rule = "FREQ=YEARLY{0!s};BYMONTH={1!s}{2!s}"\ - .format(dayString, rule['month'], endString) + endString = "" + new_rule = "FREQ=YEARLY{0!s};BYMONTH={1!s}{2!s}".format(dayString, rule["month"], endString) - comp.add('rrule').value = new_rule + comp.add("rrule").value = new_rule tzinfo = property(gettzinfo, settzinfo) # prevent Component's __setattr__ from overriding the tzinfo property - normal_attributes = Component.normal_attributes + ['tzinfo'] + normal_attributes = Component.normal_attributes + ["tzinfo"] @staticmethod def pickTzid(tzinfo, allowUTC=False): @@ -334,15 +348,15 @@ def pickTzid(tzinfo, allowUTC=False): # If tzinfo is UTC, we don't need a TZID return None # try PyICU's tzid key - if hasattr(tzinfo, 'tzid'): + if hasattr(tzinfo, "tzid"): return toUnicode(tzinfo.tzid) # try pytz zone key - if hasattr(tzinfo, 'zone'): + if hasattr(tzinfo, "zone"): return toUnicode(tzinfo.zone) # try tzical's tzid key - elif hasattr(tzinfo, '_tzid'): + elif hasattr(tzinfo, "_tzid"): return toUnicode(tzinfo._tzid) else: # return tzname for standard (non-DST) time @@ -352,20 +366,19 @@ def pickTzid(tzinfo, allowUTC=False): if tzinfo.dst(dt) == notDST: return toUnicode(tzinfo.tzname(dt)) # there was no standard time in 2000! - raise VObjectError("Unable to guess TZID for tzinfo {0!s}" - .format(tzinfo)) + raise VObjectError("Unable to guess TZID for tzinfo {0!s}".format(tzinfo)) def __str__(self): - return "".format(getattr(self, 'tzid', 'No TZID')) + return "".format(getattr(self, "tzid", "No TZID")) def __repr__(self): return self.__str__() def prettyPrint(self, level, tabwidth): - pre = ' ' * level * tabwidth + pre = " " * level * tabwidth print(pre, self.name) print(pre, "TZID:", self.tzid) - print('') + print("") class RecurringComponent(Component): @@ -387,6 +400,7 @@ class RecurringComponent(Component): @ivar rruleset: A U{rruleset}. """ + def __init__(self, *args, **kwds): super(RecurringComponent, self).__init__(*args, **kwds) @@ -423,17 +437,17 @@ def getrruleset(self, addRDate=False): dtstart = self.due.value else: # if there's no dtstart, just return None - logging.error('failed to get dtstart with VTODO') + logging.error("failed to get dtstart with VTODO") return None except (AttributeError, KeyError): # if there's no due, just return None - logging.error('failed to find DUE at all.') + logging.error("failed to find DUE at all.") return None if name in DATENAMES: - if type(line.value[0]) == datetime.datetime: + if type(line.value[0]) is datetime.datetime: list(map(addfunc, line.value)) - elif type(line.value[0]) == datetime.date: + elif type(line.value[0]) is datetime.date: for dt in line.value: addfunc(datetime.datetime(dt.year, dt.month, dt.day)) else: @@ -442,11 +456,10 @@ def getrruleset(self, addRDate=False): elif name in RULENAMES: # a Ruby iCalendar library escapes semi-colons in rrules, # so also remove any backslashes - value = line.value.replace('\\', '') + value = line.value.replace("\\", "") # If dtstart has no time zone, `until` # shouldn't get one, either: - ignoretz = (not isinstance(dtstart, datetime.datetime) or - dtstart.tzinfo is None) + ignoretz = not isinstance(dtstart, datetime.datetime) or dtstart.tzinfo is None try: until = rrule.rrulestr(value, ignoretz=ignoretz)._until except ValueError: @@ -457,16 +470,16 @@ def getrruleset(self, addRDate=False): utc_now = datetime.datetime.now(datetime.timezone.utc) until = rrule.rrulestr(value, dtstart=utc_now)._until - if until is not None and isinstance(dtstart, - datetime.datetime) and \ - (until.tzinfo != dtstart.tzinfo): + if ( + until is not None + and isinstance(dtstart, datetime.datetime) + and (until.tzinfo != dtstart.tzinfo) + ): # dateutil converts the UNTIL date to a datetime, # check to see if the UNTIL parameter value was a date - vals = dict(pair.split('=') for pair in - value.upper().split(';')) - if len(vals.get('UNTIL', '')) == 8: - until = datetime.datetime.combine(until.date(), - dtstart.time()) + vals = dict(pair.split("=") for pair in value.upper().split(";")) + if len(vals.get("UNTIL", "")) == 8: + until = datetime.datetime.combine(until.date(), dtstart.time()) # While RFC2445 says UNTIL MUST be UTC, Chandler allows # floating recurring events, and uses floating UNTIL # values. Also, some odd floating UNTIL but timezoned @@ -493,17 +506,16 @@ def getrruleset(self, addRDate=False): if dtstart.tzinfo is None: until = until.replace(tzinfo=None) - value_without_until = ';'.join( - pair for pair in value.split(';') - if pair.split('=')[0].upper() != 'UNTIL') - rule = rrule.rrulestr(value_without_until, - dtstart=dtstart, ignoretz=ignoretz) + value_without_until = ";".join( + pair for pair in value.split(";") if pair.split("=")[0].upper() != "UNTIL" + ) + rule = rrule.rrulestr(value_without_until, dtstart=dtstart, ignoretz=ignoretz) rule._until = until # add the rrule or exrule to the rruleset addfunc(rule) - if (name == 'rrule' or name == 'rdate') and addRDate: + if (name == "rrule" or name == "rdate") and addRDate: # rlist = rruleset._rrule if name == 'rrule' else rruleset._rdate try: # dateutils does not work with all-day @@ -515,7 +527,7 @@ def getrruleset(self, addRDate=False): else: adddtstart = dtstart - if name == 'rrule': + if name == "rrule": if rruleset._rrule[-1][0] != adddtstart: rruleset.rdate(adddtstart) added = True @@ -523,7 +535,7 @@ def getrruleset(self, addRDate=False): rruleset._rrule[-1]._count -= 1 else: added = False - elif name == 'rdate': + elif name == "rdate": if rruleset._rdate[0] != adddtstart: rruleset.rdate(adddtstart) added = True @@ -532,6 +544,7 @@ def getrruleset(self, addRDate=False): except IndexError: # it's conceivable that an rrule has 0 datetimes added = False + print(added) # todo: remove unused vars return rruleset @@ -551,15 +564,15 @@ def setrruleset(self, rruleset): untilSerialize = dateToString else: # make sure to convert time zones to UTC - untilSerialize = lambda x: dateTimeToString(x, True) + untilSerialize = partial(dateTimeToString, convertToUTC=True) for name in DATESANDRULES: if name in self.contents: del self.contents[name] - setlist = getattr(rruleset, '_' + name) + setlist = getattr(rruleset, "_" + name) if name in DATENAMES: setlist = list(setlist) # make a copy of the list - if name == 'rdate' and dtstart in setlist: + if name == "rdate" and dtstart in setlist: setlist.remove(dtstart) if isDate: setlist = [dt.date() for dt in setlist] @@ -568,28 +581,29 @@ def setrruleset(self, rruleset): elif name in RULENAMES: for rule in setlist: buf = six.StringIO() - buf.write('FREQ=') + buf.write("FREQ=") buf.write(FREQUENCIES[rule._freq]) values = {} if rule._interval != 1: - values['INTERVAL'] = [str(rule._interval)] + values["INTERVAL"] = [str(rule._interval)] if rule._wkst != 0: # wkst defaults to Monday - values['WKST'] = [WEEKDAYS[rule._wkst]] + values["WKST"] = [WEEKDAYS[rule._wkst]] if rule._bysetpos is not None: - values['BYSETPOS'] = [str(i) for i in rule._bysetpos] + values["BYSETPOS"] = [str(i) for i in rule._bysetpos] if rule._count is not None: - values['COUNT'] = [str(rule._count)] + values["COUNT"] = [str(rule._count)] elif rule._until is not None: - values['UNTIL'] = [untilSerialize(rule._until)] + values["UNTIL"] = [untilSerialize(rule._until)] days = [] - if (rule._byweekday is not None and ( - rrule.WEEKLY != rule._freq or - len(rule._byweekday) != 1 or - rule._dtstart.weekday() != rule._byweekday[0])): + if rule._byweekday is not None and ( + rrule.WEEKLY != rule._freq + or len(rule._byweekday) != 1 + or rule._dtstart.weekday() != rule._byweekday[0] + ): # ignore byweekday if freq is WEEKLY and day correlates # with dtstart because it was automatically set by dateutil days.extend(WEEKDAYS[n] for n in rule._byweekday) @@ -598,39 +612,45 @@ def setrruleset(self, rruleset): days.extend(n + WEEKDAYS[day] for day, n in rule._bynweekday) if len(days) > 0: - values['BYDAY'] = days + values["BYDAY"] = days if rule._bymonthday is not None and len(rule._bymonthday) > 0: - if not (rule._freq <= rrule.MONTHLY and - len(rule._bymonthday) == 1 and - rule._bymonthday[0] == rule._dtstart.day): + if not ( + rule._freq <= rrule.MONTHLY + and len(rule._bymonthday) == 1 + and rule._bymonthday[0] == rule._dtstart.day + ): # ignore bymonthday if it's generated by dateutil - values['BYMONTHDAY'] = [str(n) for n in rule._bymonthday] + values["BYMONTHDAY"] = [str(n) for n in rule._bymonthday] if rule._bynmonthday is not None and len(rule._bynmonthday) > 0: - values.setdefault('BYMONTHDAY', []).extend(str(n) for n in rule._bynmonthday) + values.setdefault("BYMONTHDAY", []).extend(str(n) for n in rule._bynmonthday) if rule._bymonth is not None and len(rule._bymonth) > 0: - if (rule._byweekday is not None or - len(rule._bynweekday or ()) > 0 or - not (rule._freq == rrule.YEARLY and - len(rule._bymonth) == 1 and - rule._bymonth[0] == rule._dtstart.month)): + if ( + rule._byweekday is not None + or len(rule._bynweekday or ()) > 0 + or not ( + rule._freq == rrule.YEARLY + and len(rule._bymonth) == 1 + and rule._bymonth[0] == rule._dtstart.month + ) + ): # ignore bymonth if it's generated by dateutil - values['BYMONTH'] = [str(n) for n in rule._bymonth] + values["BYMONTH"] = [str(n) for n in rule._bymonth] if rule._byyearday is not None: - values['BYYEARDAY'] = [str(n) for n in rule._byyearday] + values["BYYEARDAY"] = [str(n) for n in rule._byyearday] if rule._byweekno is not None: - values['BYWEEKNO'] = [str(n) for n in rule._byweekno] + values["BYWEEKNO"] = [str(n) for n in rule._byweekno] # byhour, byminute, bysecond are always ignored for now for key, paramvals in values.items(): - buf.write(';') + buf.write(";") buf.write(key) - buf.write('=') - buf.write(','.join(paramvals)) + buf.write("=") + buf.write(",".join(paramvals)) self.add(name).value = buf.getvalue() @@ -640,7 +660,7 @@ def __setattr__(self, name, value): """ For convenience, make self.contents directly accessible. """ - if name == 'rruleset': + if name == "rruleset": self.setrruleset(value) else: super(RecurringComponent, self).__setattr__(name, value) @@ -653,7 +673,8 @@ class TextBehavior(behavior.Behavior): TextBehavior also deals with base64 encoding if the ENCODING parameter is explicitly set to BASE64. """ - base64string = 'BASE64' # vCard uses B + + base64string = "BASE64" # vCard uses B @classmethod def decode(cls, line): @@ -661,7 +682,7 @@ def decode(cls, line): Remove backslash escaping from line.value. """ if line.encoded: - encoding = getattr(line, 'encoding_param', None) + encoding = getattr(line, "encoding_param", None) if encoding and encoding.upper() == cls.base64string: line.value = base64.b64decode(line.value) else: @@ -674,9 +695,9 @@ def encode(cls, line): Backslash escape line.value. """ if not line.encoded: - encoding = getattr(line, 'encoding_param', None) + encoding = getattr(line, "encoding_param", None) if encoding and encoding.upper() == cls.base64string: - line.value = base64.b64encode(line.value.encode('utf-8')).decode('utf-8').replace('\n', '') + line.value = base64.b64encode(line.value.encode("utf-8")).decode("utf-8").replace("\n", "") else: line.value = backslashEscape(line.value) line.encoded = True @@ -691,6 +712,7 @@ class RecurringBehavior(VCalendarComponentBehavior): """ Parent Behavior for components which should be RecurringComponents. """ + hasNative = True @staticmethod @@ -699,14 +721,14 @@ def transformToNative(obj): Turn a recurring Component into a RecurringComponent. """ if not obj.isNative: - object.__setattr__(obj, '__class__', RecurringComponent) + object.__setattr__(obj, "__class__", RecurringComponent) obj.isNative = True return obj @staticmethod def transformFromNative(obj): if obj.isNative: - object.__setattr__(obj, '__class__', Component) + object.__setattr__(obj, "__class__", Component) obj.isNative = False return obj @@ -717,23 +739,23 @@ def generateImplicitParameters(obj): This is just a dummy implementation, for now. """ - if not hasattr(obj, 'uid'): + if not hasattr(obj, "uid"): rand = int(random.random() * 100000) now = datetime.datetime.now(utc) now = dateTimeToString(now) host = socket.gethostname() - obj.add(ContentLine('UID', [], "{0} - {1}@{2}".format(now, rand, - host))) + obj.add(ContentLine("UID", [], "{0} - {1}@{2}".format(now, rand, host))) - if not hasattr(obj, 'dtstamp'): + if not hasattr(obj, "dtstamp"): now = datetime.datetime.now(utc) - obj.add('dtstamp').value = now + obj.add("dtstamp").value = now class DateTimeBehavior(behavior.Behavior): """ Parent Behavior for ContentLines containing one DATE-TIME. """ + hasNative = True @staticmethod @@ -749,17 +771,17 @@ def transformToNative(obj): if obj.isNative: return obj obj.isNative = True - if obj.value == '': + if obj.value == "": return obj obj.value = obj.value # we're cheating a little here, parseDtstart allows DATE obj.value = parseDtstart(obj) if obj.value.tzinfo is None: - obj.params['X-VOBJ-FLOATINGTIME-ALLOWED'] = ['TRUE'] - if obj.params.get('TZID'): + obj.params["X-VOBJ-FLOATINGTIME-ALLOWED"] = ["TRUE"] + if obj.params.get("TZID"): # Keep a copy of the original TZID around - obj.params['X-VOBJ-ORIGINAL-TZID'] = [obj.params['TZID']] - del obj.params['TZID'] + obj.params["X-VOBJ-ORIGINAL-TZID"] = [obj.params["TZID"]] + del obj.params["TZID"] return obj @classmethod @@ -773,10 +795,10 @@ def transformFromNative(cls, obj): obj.value = dateTimeToString(obj.value, cls.forceUTC) if not cls.forceUTC and tzid is not None: obj.tzid_param = tzid - if obj.params.get('X-VOBJ-ORIGINAL-TZID'): - if not hasattr(obj, 'tzid_param'): + if obj.params.get("X-VOBJ-ORIGINAL-TZID"): + if not hasattr(obj, "tzid_param"): obj.tzid_param = obj.x_vobj_original_tzid_param - del obj.params['X-VOBJ-ORIGINAL-TZID'] + del obj.params["X-VOBJ-ORIGINAL-TZID"] return obj @@ -785,6 +807,7 @@ class UTCDateTimeBehavior(DateTimeBehavior): """ A value which must be specified in UTC. """ + forceUTC = True @@ -792,6 +815,7 @@ class DateOrDateTimeBehavior(behavior.Behavior): """ Parent Behavior for ContentLines containing one DATE or DATE-TIME. """ + hasNative = True @staticmethod @@ -802,14 +826,14 @@ def transformToNative(obj): if obj.isNative: return obj obj.isNative = True - if obj.value == '': + if obj.value == "": return obj obj.value = obj.value obj.value = parseDtstart(obj, allowSignatureMismatch=True) - if getattr(obj, 'value_param', 'DATE-TIME').upper() == 'DATE-TIME': - if hasattr(obj, 'tzid_param'): + if getattr(obj, "value_param", "DATE-TIME").upper() == "DATE-TIME": + if hasattr(obj, "tzid_param"): # Keep a copy of the original TZID around - obj.params['X-VOBJ-ORIGINAL-TZID'] = [obj.tzid_param] + obj.params["X-VOBJ-ORIGINAL-TZID"] = [obj.tzid_param] del obj.tzid_param return obj @@ -818,9 +842,9 @@ def transformFromNative(obj): """ Replace the date or datetime in obj.value with an ISO 8601 string. """ - if type(obj.value) == datetime.date: + if type(obj.value) is datetime.date: obj.isNative = False - obj.value_param = 'DATE' + obj.value_param = "DATE" obj.value = dateToString(obj.value) return obj else: @@ -832,6 +856,7 @@ class MultiDateBehavior(behavior.Behavior): Parent Behavior for ContentLines containing one or more DATE, DATE-TIME, or PERIOD. """ + hasNative = True @staticmethod @@ -843,11 +868,11 @@ def transformToNative(obj): if obj.isNative: return obj obj.isNative = True - if obj.value == '': + if obj.value == "": obj.value = [] return obj - tzinfo = getTzid(getattr(obj, 'tzid_param', None)) - valueParam = getattr(obj, 'value_param', "DATE-TIME").upper() + tzinfo = getTzid(getattr(obj, "tzid_param", None)) + valueParam = getattr(obj, "value_param", "DATE-TIME").upper() valTexts = obj.value.split(",") if valueParam == "DATE": obj.value = [stringToDate(x) for x in valTexts] @@ -863,10 +888,10 @@ def transformFromNative(obj): Replace the date, datetime or period tuples in obj.value with appropriate strings. """ - if obj.value and type(obj.value[0]) == datetime.date: + if obj.value and type(obj.value[0]) is datetime.date: obj.isNative = False - obj.value_param = 'DATE' - obj.value = ','.join([dateToString(val) for val in obj.value]) + obj.value_param = "DATE" + obj.value = ",".join([dateToString(val) for val in obj.value]) return obj # Fixme: handle PERIOD case else: @@ -875,12 +900,12 @@ def transformFromNative(obj): transformed = [] tzid = None for val in obj.value: - if tzid is None and type(val) == datetime.datetime: + if tzid is None and type(val) is datetime.datetime: tzid = TimezoneComponent.registerTzinfo(val.tzinfo) if tzid is not None: obj.tzid_param = tzid transformed.append(dateTimeToString(val)) - obj.value = ','.join(transformed) + obj.value = ",".join(transformed) return obj @@ -890,6 +915,7 @@ class MultiTextBehavior(behavior.Behavior): After transformation, value is a list of strings. """ + listSeparator = "," @classmethod @@ -898,8 +924,7 @@ def decode(cls, line): Remove backslash escaping from line.value, then split on commas. """ if line.encoded: - line.value = stringToTextValues(line.value, - listSeparator=cls.listSeparator) + line.value = stringToTextValues(line.value, listSeparator=cls.listSeparator) line.encoded = False @classmethod @@ -908,8 +933,7 @@ def encode(cls, line): Backslash escape line.value. """ if not line.encoded: - line.value = cls.listSeparator.join(backslashEscape(val) - for val in line.value) + line.value = cls.listSeparator.join(backslashEscape(val) for val in line.value) line.encoded = True @@ -922,21 +946,22 @@ class VCalendar2_0(VCalendarComponentBehavior): """ vCalendar 2.0 behavior. With added VAVAILABILITY support. """ - name = 'VCALENDAR' - description = 'vCalendar 2.0, also known as iCalendar.' - versionString = '2.0' - sortFirst = ('version', 'calscale', 'method', 'prodid', 'vtimezone') + + name = "VCALENDAR" + description = "vCalendar 2.0, also known as iCalendar." + versionString = "2.0" + sortFirst = ("version", "calscale", "method", "prodid", "vtimezone") knownChildren = { - 'CALSCALE': (0, 1, None), # min, max, behaviorRegistry id - 'METHOD': (0, 1, None), - 'VERSION': (0, 1, None), # required, but auto-generated - 'PRODID': (1, 1, None), - 'VTIMEZONE': (0, None, None), - 'VEVENT': (0, None, None), - 'VTODO': (0, None, None), - 'VJOURNAL': (0, None, None), - 'VFREEBUSY': (0, None, None), - 'VAVAILABILITY': (0, None, None), + "CALSCALE": (0, 1, None), # min, max, behaviorRegistry id + "METHOD": (0, 1, None), + "VERSION": (0, 1, None), # required, but auto-generated + "PRODID": (1, 1, None), + "VTIMEZONE": (0, None, None), + "VEVENT": (0, None, None), + "VTODO": (0, None, None), + "VJOURNAL": (0, None, None), + "VFREEBUSY": (0, None, None), + "VAVAILABILITY": (0, None, None), } @classmethod @@ -950,38 +975,37 @@ def generateImplicitParameters(cls, obj): for comp in obj.components(): if comp.behavior is not None: comp.behavior.generateImplicitParameters(comp) - if not hasattr(obj, 'prodid'): - obj.add(ContentLine('PRODID', [], PRODID)) - if not hasattr(obj, 'version'): - obj.add(ContentLine('VERSION', [], cls.versionString)) + if not hasattr(obj, "prodid"): + obj.add(ContentLine("PRODID", [], PRODID)) + if not hasattr(obj, "version"): + obj.add(ContentLine("VERSION", [], cls.versionString)) tzidsUsed = {} def findTzids(obj, table): - if isinstance(obj, ContentLine) and (obj.behavior is None or - not obj.behavior.forceUTC): - if getattr(obj, 'tzid_param', None): + if isinstance(obj, ContentLine) and (obj.behavior is None or not obj.behavior.forceUTC): + if getattr(obj, "tzid_param", None): table[obj.tzid_param] = 1 else: - if type(obj.value) == list: - for item in obj.value: - tzinfo = getattr(obj.value, 'tzinfo', None) + if type(obj.value) is list: + for _ in obj.value: + tzinfo = getattr(obj.value, "tzinfo", None) tzid = TimezoneComponent.registerTzinfo(tzinfo) if tzid: table[tzid] = 1 else: - tzinfo = getattr(obj.value, 'tzinfo', None) + tzinfo = getattr(obj.value, "tzinfo", None) tzid = TimezoneComponent.registerTzinfo(tzinfo) if tzid: table[tzid] = 1 for child in obj.getChildren(): - if obj.name != 'VTIMEZONE': + if obj.name != "VTIMEZONE": findTzids(child, table) findTzids(obj, tzidsUsed) - oldtzids = [toUnicode(x.tzid.value) for x in getattr(obj, 'vtimezone_list', [])] + oldtzids = [toUnicode(x.tzid.value) for x in getattr(obj, "vtimezone_list", [])] for tzid in tzidsUsed.keys(): tzid = toUnicode(tzid) - if tzid != u'UTC' and tzid not in oldtzids: + if tzid != "UTC" and tzid not in oldtzids: obj.add(TimezoneComponent(tzinfo=getTzid(tzid))) @classmethod @@ -1006,28 +1030,38 @@ def serialize(cls, obj, buf, lineLength, validate=True): transformed = obj undoTransform = False out = None + print(transformed, out) # todo: remove unused vars outbuf = buf or six.StringIO() if obj.group is None: - groupString = '' + groupString = "" else: - groupString = obj.group + '.' + groupString = obj.group + "." if obj.useBegin: - foldOneLine(outbuf, "{0}BEGIN:{1}".format(groupString, obj.name), - lineLength) + foldOneLine(outbuf, "{0}BEGIN:{1}".format(groupString, obj.name), lineLength) try: - first_props = [s for s in cls.sortFirst if s in obj.contents \ - and not isinstance(obj.contents[s][0], Component)] - first_components = [s for s in cls.sortFirst if s in obj.contents \ - and isinstance(obj.contents[s][0], Component)] + first_props = [ + s for s in cls.sortFirst if s in obj.contents and not isinstance(obj.contents[s][0], Component) + ] + first_components = [ + s for s in cls.sortFirst if s in obj.contents and isinstance(obj.contents[s][0], Component) + ] except Exception: first_props = first_components = [] # first_components = [] - prop_keys = sorted(list(k for k in obj.contents.keys() if k not in first_props \ - and not isinstance(obj.contents[k][0], Component))) - comp_keys = sorted(list(k for k in obj.contents.keys() if k not in first_components \ - and isinstance(obj.contents[k][0], Component))) + prop_keys = sorted( + list( + k for k in obj.contents.keys() if k not in first_props and not isinstance(obj.contents[k][0], Component) + ) + ) + comp_keys = sorted( + list( + k + for k in obj.contents.keys() + if k not in first_components and isinstance(obj.contents[k][0], Component) + ) + ) sorted_keys = first_props + prop_keys + first_components + comp_keys children = [o for k in sorted_keys for o in obj.contents[k]] @@ -1036,12 +1070,13 @@ def serialize(cls, obj, buf, lineLength, validate=True): # validate is recursive, we only need to validate once child.serialize(outbuf, lineLength, validate=False) if obj.useBegin: - foldOneLine(outbuf, "{0}END:{1}".format(groupString, obj.name), - lineLength) + foldOneLine(outbuf, "{0}END:{1}".format(groupString, obj.name), lineLength) out = buf or outbuf.getvalue() if undoTransform: obj.transformToNative() return out + + registerBehavior(VCalendar2_0) @@ -1049,26 +1084,27 @@ class VTimezone(VCalendarComponentBehavior): """ Timezone behavior. """ - name = 'VTIMEZONE' + + name = "VTIMEZONE" hasNative = True - description = 'A grouping of component properties that defines a time zone.' - sortFirst = ('tzid', 'last-modified', 'tzurl', 'standard', 'daylight') + description = "A grouping of component properties that defines a time zone." + sortFirst = ("tzid", "last-modified", "tzurl", "standard", "daylight") knownChildren = { - 'TZID': (1, 1, None), # min, max, behaviorRegistry id - 'LAST-MODIFIED': (0, 1, None), - 'TZURL': (0, 1, None), - 'STANDARD': (0, None, None), # NOTE: One of Standard or - 'DAYLIGHT': (0, None, None) # Daylight must appear + "TZID": (1, 1, None), # min, max, behaviorRegistry id + "LAST-MODIFIED": (0, 1, None), + "TZURL": (0, 1, None), + "STANDARD": (0, None, None), # NOTE: One of Standard or + "DAYLIGHT": (0, None, None), # Daylight must appear } @classmethod def validate(cls, obj, raiseException, *args): - if not hasattr(obj, 'tzid') or obj.tzid.value is None: + if not hasattr(obj, "tzid") or obj.tzid.value is None: if raiseException: m = "VTIMEZONE components must contain a valid TZID" raise ValidateError(m) return False - if 'standard' in obj.contents or 'daylight' in obj.contents: + if "standard" in obj.contents or "daylight" in obj.contents: return super(VTimezone, cls).validate(obj, raiseException, *args) else: if raiseException: @@ -1080,7 +1116,7 @@ def validate(cls, obj, raiseException, *args): @staticmethod def transformToNative(obj): if not obj.isNative: - object.__setattr__(obj, '__class__', TimezoneComponent) + object.__setattr__(obj, "__class__", TimezoneComponent) obj.isNative = True obj.registerTzinfo(obj.tzinfo) return obj @@ -1088,6 +1124,8 @@ def transformToNative(obj): @staticmethod def transformFromNative(obj): return obj + + registerBehavior(VTimezone) @@ -1101,66 +1139,69 @@ class TZID(behavior.Behavior): do we want to escape them. Leaving them alone works for Microsoft's breakage, and doesn't affect compliant iCalendar streams. """ + + registerBehavior(TZID) class DaylightOrStandard(VCalendarComponentBehavior): hasNative = False - knownChildren = {'DTSTART': (1, 1, None), # min, max, behaviorRegistry id - 'RRULE': (0, 1, None)} + knownChildren = {"DTSTART": (1, 1, None), "RRULE": (0, 1, None)} # min, max, behaviorRegistry id + -registerBehavior(DaylightOrStandard, 'STANDARD') -registerBehavior(DaylightOrStandard, 'DAYLIGHT') +registerBehavior(DaylightOrStandard, "STANDARD") +registerBehavior(DaylightOrStandard, "DAYLIGHT") class VEvent(RecurringBehavior): """ Event behavior. """ - name = 'VEVENT' - sortFirst = ('uid', 'recurrence-id', 'dtstart', 'duration', 'dtend') + + name = "VEVENT" + sortFirst = ("uid", "recurrence-id", "dtstart", "duration", "dtend") description = 'A grouping of component properties, and possibly including \ "VALARM" calendar components, that represents a scheduled \ amount of time on a calendar.' knownChildren = { - 'DTSTART': (0, 1, None), # min, max, behaviorRegistry id - 'CLASS': (0, 1, None), - 'CREATED': (0, 1, None), - 'DESCRIPTION': (0, 1, None), - 'GEO': (0, 1, None), - 'LAST-MODIFIED': (0, 1, None), - 'LOCATION': (0, 1, None), - 'ORGANIZER': (0, 1, None), - 'PRIORITY': (0, 1, None), - 'DTSTAMP': (1, 1, None), # required - 'SEQUENCE': (0, 1, None), - 'STATUS': (0, 1, None), - 'SUMMARY': (0, 1, None), - 'TRANSP': (0, 1, None), - 'UID': (1, 1, None), - 'URL': (0, 1, None), - 'RECURRENCE-ID': (0, 1, None), - 'DTEND': (0, 1, None), # NOTE: Only one of DtEnd or - 'DURATION': (0, 1, None), # Duration can appear - 'ATTACH': (0, None, None), - 'ATTENDEE': (0, None, None), - 'CATEGORIES': (0, None, None), - 'COMMENT': (0, None, None), - 'CONTACT': (0, None, None), - 'EXDATE': (0, None, None), - 'EXRULE': (0, None, None), - 'REQUEST-STATUS': (0, None, None), - 'RELATED-TO': (0, None, None), - 'RESOURCES': (0, None, None), - 'RDATE': (0, None, None), - 'RRULE': (0, None, None), - 'VALARM': (0, None, None) + "DTSTART": (0, 1, None), # min, max, behaviorRegistry id + "CLASS": (0, 1, None), + "CREATED": (0, 1, None), + "DESCRIPTION": (0, 1, None), + "GEO": (0, 1, None), + "LAST-MODIFIED": (0, 1, None), + "LOCATION": (0, 1, None), + "ORGANIZER": (0, 1, None), + "PRIORITY": (0, 1, None), + "DTSTAMP": (1, 1, None), # required + "SEQUENCE": (0, 1, None), + "STATUS": (0, 1, None), + "SUMMARY": (0, 1, None), + "TRANSP": (0, 1, None), + "UID": (1, 1, None), + "URL": (0, 1, None), + "RECURRENCE-ID": (0, 1, None), + "DTEND": (0, 1, None), # NOTE: Only one of DtEnd or + "DURATION": (0, 1, None), # Duration can appear + "ATTACH": (0, None, None), + "ATTENDEE": (0, None, None), + "CATEGORIES": (0, None, None), + "COMMENT": (0, None, None), + "CONTACT": (0, None, None), + "EXDATE": (0, None, None), + "EXRULE": (0, None, None), + "REQUEST-STATUS": (0, None, None), + "RELATED-TO": (0, None, None), + "RESOURCES": (0, None, None), + "RDATE": (0, None, None), + "RRULE": (0, None, None), + "VALARM": (0, None, None), } @classmethod def validate(cls, obj, raiseException, *args): - if 'dtend' in obj.contents and 'duration' in obj.contents: + if "dtend" in obj.contents and "duration" in obj.contents: if raiseException: m = "VEVENT components cannot contain both DTEND and DURATION\ components" @@ -1169,6 +1210,7 @@ def validate(cls, obj, raiseException, *args): else: return super(VEvent, cls).validate(obj, raiseException, *args) + registerBehavior(VEvent) @@ -1176,49 +1218,50 @@ class VTodo(RecurringBehavior): """ To-do behavior. """ - name = 'VTODO' + + name = "VTODO" description = 'A grouping of component properties and possibly "VALARM" \ calendar components that represent an action-item or \ assignment.' knownChildren = { - 'DTSTART': (0, 1, None), # min, max, behaviorRegistry id - 'CLASS': (0, 1, None), - 'COMPLETED': (0, 1, None), - 'CREATED': (0, 1, None), - 'DESCRIPTION': (0, 1, None), - 'GEO': (0, 1, None), - 'LAST-MODIFIED': (0, 1, None), - 'LOCATION': (0, 1, None), - 'ORGANIZER': (0, 1, None), - 'PERCENT': (0, 1, None), - 'PRIORITY': (0, 1, None), - 'DTSTAMP': (1, 1, None), - 'SEQUENCE': (0, 1, None), - 'STATUS': (0, 1, None), - 'SUMMARY': (0, 1, None), - 'UID': (0, 1, None), - 'URL': (0, 1, None), - 'RECURRENCE-ID': (0, 1, None), - 'DUE': (0, 1, None), # NOTE: Only one of Due or - 'DURATION': (0, 1, None), # Duration can appear - 'ATTACH': (0, None, None), - 'ATTENDEE': (0, None, None), - 'CATEGORIES': (0, None, None), - 'COMMENT': (0, None, None), - 'CONTACT': (0, None, None), - 'EXDATE': (0, None, None), - 'EXRULE': (0, None, None), - 'REQUEST-STATUS': (0, None, None), - 'RELATED-TO': (0, None, None), - 'RESOURCES': (0, None, None), - 'RDATE': (0, None, None), - 'RRULE': (0, None, None), - 'VALARM': (0, None, None) + "DTSTART": (0, 1, None), # min, max, behaviorRegistry id + "CLASS": (0, 1, None), + "COMPLETED": (0, 1, None), + "CREATED": (0, 1, None), + "DESCRIPTION": (0, 1, None), + "GEO": (0, 1, None), + "LAST-MODIFIED": (0, 1, None), + "LOCATION": (0, 1, None), + "ORGANIZER": (0, 1, None), + "PERCENT": (0, 1, None), + "PRIORITY": (0, 1, None), + "DTSTAMP": (1, 1, None), + "SEQUENCE": (0, 1, None), + "STATUS": (0, 1, None), + "SUMMARY": (0, 1, None), + "UID": (0, 1, None), + "URL": (0, 1, None), + "RECURRENCE-ID": (0, 1, None), + "DUE": (0, 1, None), # NOTE: Only one of Due or + "DURATION": (0, 1, None), # Duration can appear + "ATTACH": (0, None, None), + "ATTENDEE": (0, None, None), + "CATEGORIES": (0, None, None), + "COMMENT": (0, None, None), + "CONTACT": (0, None, None), + "EXDATE": (0, None, None), + "EXRULE": (0, None, None), + "REQUEST-STATUS": (0, None, None), + "RELATED-TO": (0, None, None), + "RESOURCES": (0, None, None), + "RDATE": (0, None, None), + "RRULE": (0, None, None), + "VALARM": (0, None, None), } @classmethod def validate(cls, obj, raiseException, *args): - if 'due' in obj.contents and 'duration' in obj.contents: + if "due" in obj.contents and "duration" in obj.contents: if raiseException: m = "VTODO components cannot contain both DUE and DURATION\ components" @@ -1227,6 +1270,7 @@ def validate(cls, obj, raiseException, *args): else: return super(VTodo, cls).validate(obj, raiseException, *args) + registerBehavior(VTodo) @@ -1234,33 +1278,36 @@ class VJournal(RecurringBehavior): """ Journal entry behavior. """ - name = 'VJOURNAL' + + name = "VJOURNAL" knownChildren = { - 'DTSTART': (0, 1, None), # min, max, behaviorRegistry id - 'CLASS': (0, 1, None), - 'CREATED': (0, 1, None), - 'DESCRIPTION': (0, 1, None), - 'LAST-MODIFIED': (0, 1, None), - 'ORGANIZER': (0, 1, None), - 'DTSTAMP': (1, 1, None), - 'SEQUENCE': (0, 1, None), - 'STATUS': (0, 1, None), - 'SUMMARY': (0, 1, None), - 'UID': (0, 1, None), - 'URL': (0, 1, None), - 'RECURRENCE-ID': (0, 1, None), - 'ATTACH': (0, None, None), - 'ATTENDEE': (0, None, None), - 'CATEGORIES': (0, None, None), - 'COMMENT': (0, None, None), - 'CONTACT': (0, None, None), - 'EXDATE': (0, None, None), - 'EXRULE': (0, None, None), - 'REQUEST-STATUS': (0, None, None), - 'RELATED-TO': (0, None, None), - 'RDATE': (0, None, None), - 'RRULE': (0, None, None) + "DTSTART": (0, 1, None), # min, max, behaviorRegistry id + "CLASS": (0, 1, None), + "CREATED": (0, 1, None), + "DESCRIPTION": (0, 1, None), + "LAST-MODIFIED": (0, 1, None), + "ORGANIZER": (0, 1, None), + "DTSTAMP": (1, 1, None), + "SEQUENCE": (0, 1, None), + "STATUS": (0, 1, None), + "SUMMARY": (0, 1, None), + "UID": (0, 1, None), + "URL": (0, 1, None), + "RECURRENCE-ID": (0, 1, None), + "ATTACH": (0, None, None), + "ATTENDEE": (0, None, None), + "CATEGORIES": (0, None, None), + "COMMENT": (0, None, None), + "CONTACT": (0, None, None), + "EXDATE": (0, None, None), + "EXRULE": (0, None, None), + "REQUEST-STATUS": (0, None, None), + "RELATED-TO": (0, None, None), + "RDATE": (0, None, None), + "RRULE": (0, None, None), } + + registerBehavior(VJournal) @@ -1268,26 +1315,28 @@ class VFreeBusy(VCalendarComponentBehavior): """ Free/busy state behavior. """ - name = 'VFREEBUSY' - description = 'A grouping of component properties that describe either a \ + + name = "VFREEBUSY" + description = "A grouping of component properties that describe either a \ request for free/busy time, describe a response to a request \ - for free/busy time or describe a published set of busy time.' - sortFirst = ('uid', 'dtstart', 'duration', 'dtend') + for free/busy time or describe a published set of busy time." + sortFirst = ("uid", "dtstart", "duration", "dtend") knownChildren = { - 'DTSTART': (0, 1, None), # min, max, behaviorRegistry id - 'CONTACT': (0, 1, None), - 'DTEND': (0, 1, None), - 'DURATION': (0, 1, None), - 'ORGANIZER': (0, 1, None), - 'DTSTAMP': (1, 1, None), - 'UID': (0, 1, None), - 'URL': (0, 1, None), - 'ATTENDEE': (0, None, None), - 'COMMENT': (0, None, None), - 'FREEBUSY': (0, None, None), - 'REQUEST-STATUS': (0, None, None) + "DTSTART": (0, 1, None), # min, max, behaviorRegistry id + "CONTACT": (0, 1, None), + "DTEND": (0, 1, None), + "DURATION": (0, 1, None), + "ORGANIZER": (0, 1, None), + "DTSTAMP": (1, 1, None), + "UID": (0, 1, None), + "URL": (0, 1, None), + "ATTENDEE": (0, None, None), + "COMMENT": (0, None, None), + "FREEBUSY": (0, None, None), + "REQUEST-STATUS": (0, None, None), } + registerBehavior(VFreeBusy) @@ -1295,15 +1344,16 @@ class VAlarm(VCalendarComponentBehavior): """ Alarm behavior. """ - name = 'VALARM' - description = 'Alarms describe when and how to provide alerts about events \ - and to-dos.' + + name = "VALARM" + description = "Alarms describe when and how to provide alerts about events \ + and to-dos." knownChildren = { - 'ACTION': (1, 1, None), # min, max, behaviorRegistry id - 'TRIGGER': (1, 1, None), - 'DURATION': (0, 1, None), - 'REPEAT': (0, 1, None), - 'DESCRIPTION': (0, 1, None) + "ACTION": (1, 1, None), # min, max, behaviorRegistry id + "TRIGGER": (1, 1, None), + "DURATION": (0, 1, None), + "REPEAT": (0, 1, None), + "DESCRIPTION": (0, 1, None), } @staticmethod @@ -1314,11 +1364,11 @@ def generateImplicitParameters(obj): try: obj.action except AttributeError: - obj.add('action').value = 'AUDIO' + obj.add("action").value = "AUDIO" try: obj.trigger except AttributeError: - obj.add('trigger').value = datetime.timedelta(0) + obj.add("trigger").value = datetime.timedelta(0) @classmethod def validate(cls, obj, raiseException, *args): @@ -1335,6 +1385,7 @@ def validate(cls, obj, raiseException, *args): """ return True + registerBehavior(VAlarm) @@ -1344,31 +1395,32 @@ class VAvailability(VCalendarComponentBehavior): Used to represent user's available time slots. """ - name = 'VAVAILABILITY' - description = 'A component used to represent a user\'s available time slots.' - sortFirst = ('uid', 'dtstart', 'duration', 'dtend') + + name = "VAVAILABILITY" + description = "A component used to represent a user's available time slots." + sortFirst = ("uid", "dtstart", "duration", "dtend") knownChildren = { - 'UID': (1, 1, None), # min, max, behaviorRegistry id - 'DTSTAMP': (1, 1, None), - 'BUSYTYPE': (0, 1, None), - 'CREATED': (0, 1, None), - 'DTSTART': (0, 1, None), - 'LAST-MODIFIED': (0, 1, None), - 'ORGANIZER': (0, 1, None), - 'SEQUENCE': (0, 1, None), - 'SUMMARY': (0, 1, None), - 'URL': (0, 1, None), - 'DTEND': (0, 1, None), - 'DURATION': (0, 1, None), - 'CATEGORIES': (0, None, None), - 'COMMENT': (0, None, None), - 'CONTACT': (0, None, None), - 'AVAILABLE': (0, None, None), + "UID": (1, 1, None), # min, max, behaviorRegistry id + "DTSTAMP": (1, 1, None), + "BUSYTYPE": (0, 1, None), + "CREATED": (0, 1, None), + "DTSTART": (0, 1, None), + "LAST-MODIFIED": (0, 1, None), + "ORGANIZER": (0, 1, None), + "SEQUENCE": (0, 1, None), + "SUMMARY": (0, 1, None), + "URL": (0, 1, None), + "DTEND": (0, 1, None), + "DURATION": (0, 1, None), + "CATEGORIES": (0, None, None), + "COMMENT": (0, None, None), + "CONTACT": (0, None, None), + "AVAILABLE": (0, None, None), } @classmethod def validate(cls, obj, raiseException, *args): - if 'dtend' in obj.contents and 'duration' in obj.contents: + if "dtend" in obj.contents and "duration" in obj.contents: if raiseException: m = "VAVAILABILITY components cannot contain both DTEND and DURATION components" raise ValidateError(m) @@ -1376,6 +1428,7 @@ def validate(cls, obj, raiseException, *args): else: return super(VAvailability, cls).validate(obj, raiseException, *args) + registerBehavior(VAvailability) @@ -1383,31 +1436,32 @@ class Available(RecurringBehavior): """ Event behavior. """ - name = 'AVAILABLE' - sortFirst = ('uid', 'recurrence-id', 'dtstart', 'duration', 'dtend') - description = 'Defines a period of time in which a user is normally available.' + + name = "AVAILABLE" + sortFirst = ("uid", "recurrence-id", "dtstart", "duration", "dtend") + description = "Defines a period of time in which a user is normally available." knownChildren = { - 'DTSTAMP': (1, 1, None), # min, max, behaviorRegistry id - 'DTSTART': (1, 1, None), - 'UID': (1, 1, None), - 'DTEND': (0, 1, None), # NOTE: One of DtEnd or - 'DURATION': (0, 1, None), # Duration must appear, but not both - 'CREATED': (0, 1, None), - 'LAST-MODIFIED': (0, 1, None), - 'RECURRENCE-ID': (0, 1, None), - 'RRULE': (0, 1, None), - 'SUMMARY': (0, 1, None), - 'CATEGORIES': (0, None, None), - 'COMMENT': (0, None, None), - 'CONTACT': (0, None, None), - 'EXDATE': (0, None, None), - 'RDATE': (0, None, None), + "DTSTAMP": (1, 1, None), # min, max, behaviorRegistry id + "DTSTART": (1, 1, None), + "UID": (1, 1, None), + "DTEND": (0, 1, None), # NOTE: One of DtEnd or + "DURATION": (0, 1, None), # Duration must appear, but not both + "CREATED": (0, 1, None), + "LAST-MODIFIED": (0, 1, None), + "RECURRENCE-ID": (0, 1, None), + "RRULE": (0, 1, None), + "SUMMARY": (0, 1, None), + "CATEGORIES": (0, None, None), + "COMMENT": (0, None, None), + "CONTACT": (0, None, None), + "EXDATE": (0, None, None), + "RDATE": (0, None, None), } @classmethod def validate(cls, obj, raiseException, *args): - has_dtend = 'dtend' in obj.contents - has_duration = 'duration' in obj.contents + has_dtend = "dtend" in obj.contents + has_duration = "duration" in obj.contents if has_dtend and has_duration: if raiseException: m = "AVAILABLE components cannot contain both DTEND and DURATION\ @@ -1423,6 +1477,7 @@ def validate(cls, obj, raiseException, *args): else: return super(Available, cls).validate(obj, raiseException, *args) + registerBehavior(Available) @@ -1430,7 +1485,8 @@ class Duration(behavior.Behavior): """ Behavior for Duration ContentLines. Transform to datetime.timedelta. """ - name = 'DURATION' + + name = "DURATION" hasNative = True @staticmethod @@ -1442,7 +1498,7 @@ def transformToNative(obj): return obj obj.isNative = True obj.value = obj.value - if obj.value == '': + if obj.value == "": return obj else: deltalist = stringToDurations(obj.value) @@ -1464,6 +1520,7 @@ def transformFromNative(obj): obj.value = timedeltaToString(obj.value) return obj + registerBehavior(Duration) @@ -1471,8 +1528,9 @@ class Trigger(behavior.Behavior): """ DATE-TIME or DURATION """ - name = 'TRIGGER' - description = 'This property specifies when an alarm will trigger.' + + name = "TRIGGER" + description = "This property specifies when an alarm will trigger." hasNative = True forceUTC = True @@ -1483,28 +1541,29 @@ def transformToNative(obj): """ if obj.isNative: return obj - value = getattr(obj, 'value_param', 'DURATION').upper() - if hasattr(obj, 'value_param'): + value = getattr(obj, "value_param", "DURATION").upper() + if hasattr(obj, "value_param"): del obj.value_param - if obj.value == '': + if obj.value == "": obj.isNative = True return obj - elif value == 'DURATION': + elif value == "DURATION": try: return Duration.transformToNative(obj) except ParseError: - logger.warning("TRIGGER not recognized as DURATION, trying " - "DATE-TIME, because iCal sometimes exports " - "DATE-TIMEs without setting VALUE=DATE-TIME") + logger.warning( + "TRIGGER not recognized as DURATION, trying " + "DATE-TIME, because iCal sometimes exports " + "DATE-TIMEs without setting VALUE=DATE-TIME" + ) try: obj.isNative = False dt = DateTimeBehavior.transformToNative(obj) return dt - except: - msg = "TRIGGER with no VALUE not recognized as DURATION " \ - "or as DATE-TIME" + except Exception: + msg = "TRIGGER with no VALUE not recognized as DURATION " "or as DATE-TIME" raise ParseError(msg) - elif value == 'DATE-TIME': + elif value == "DATE-TIME": # TRIGGERs with DATE-TIME values must be in UTC, we could validate # that fact, for now we take it on faith. return DateTimeBehavior.transformToNative(obj) @@ -1513,14 +1572,15 @@ def transformToNative(obj): @staticmethod def transformFromNative(obj): - if type(obj.value) == datetime.datetime: - obj.value_param = 'DATE-TIME' + if type(obj.value) is datetime.datetime: + obj.value_param = "DATE-TIME" return UTCDateTimeBehavior.transformFromNative(obj) - elif type(obj.value) == datetime.timedelta: + elif type(obj.value) is datetime.timedelta: return Duration.transformFromNative(obj) else: - raise NativeError("Native TRIGGER values must be timedelta or " - "datetime") + raise NativeError("Native TRIGGER values must be timedelta or " "datetime") + + registerBehavior(Trigger) @@ -1528,6 +1588,7 @@ class PeriodBehavior(behavior.Behavior): """ A list of (date-time, timedelta) tuples. """ + hasNative = True @staticmethod @@ -1538,10 +1599,10 @@ def transformToNative(obj): if obj.isNative: return obj obj.isNative = True - if obj.value == '': + if obj.value == "": obj.value = [] return obj - tzinfo = getTzid(getattr(obj, 'tzid_param', None)) + tzinfo = getTzid(getattr(obj, "tzid_param", None)) obj.value = [stringToPeriod(x, tzinfo) for x in obj.value.split(",")] return obj @@ -1560,7 +1621,7 @@ def transformFromNative(cls, obj): if not cls.forceUTC and tzid is not None: obj.tzid_param = tzid - obj.value = ','.join(transformed) + obj.value = ",".join(transformed) return obj @@ -1569,9 +1630,12 @@ class FreeBusy(PeriodBehavior): """ Free or busy period of time, must be specified in UTC. """ - name = 'FREEBUSY' + + name = "FREEBUSY" forceUTC = True -registerBehavior(FreeBusy, 'FREEBUSY') + + +registerBehavior(FreeBusy, "FREEBUSY") class RRule(behavior.Behavior): @@ -1579,30 +1643,42 @@ class RRule(behavior.Behavior): Dummy behavior to avoid having RRULEs being treated as text lines (and thus having semi-colons inaccurately escaped). """ -registerBehavior(RRule, 'RRULE') -registerBehavior(RRule, 'EXRULE') +registerBehavior(RRule, "RRULE") +registerBehavior(RRule, "EXRULE") + # ------------------------ Registration of common classes ---------------------- -utcDateTimeList = ['LAST-MODIFIED', 'CREATED', 'COMPLETED', 'DTSTAMP'] +utcDateTimeList = ["LAST-MODIFIED", "CREATED", "COMPLETED", "DTSTAMP"] list(map(lambda x: registerBehavior(UTCDateTimeBehavior, x), utcDateTimeList)) -dateTimeOrDateList = ['DTEND', 'DTSTART', 'DUE', 'RECURRENCE-ID'] -list(map(lambda x: registerBehavior(DateOrDateTimeBehavior, x), - dateTimeOrDateList)) - -registerBehavior(MultiDateBehavior, 'RDATE') -registerBehavior(MultiDateBehavior, 'EXDATE') - - -textList = ['CALSCALE', 'METHOD', 'PRODID', 'CLASS', 'COMMENT', 'DESCRIPTION', - 'LOCATION', 'STATUS', 'SUMMARY', 'TRANSP', 'CONTACT', 'RELATED-TO', - 'UID', 'ACTION', 'BUSYTYPE'] +dateTimeOrDateList = ["DTEND", "DTSTART", "DUE", "RECURRENCE-ID"] +list(map(lambda x: registerBehavior(DateOrDateTimeBehavior, x), dateTimeOrDateList)) + +registerBehavior(MultiDateBehavior, "RDATE") +registerBehavior(MultiDateBehavior, "EXDATE") + +textList = [ + "CALSCALE", + "METHOD", + "PRODID", + "CLASS", + "COMMENT", + "DESCRIPTION", + "LOCATION", + "STATUS", + "SUMMARY", + "TRANSP", + "CONTACT", + "RELATED-TO", + "UID", + "ACTION", + "BUSYTYPE", +] list(map(lambda x: registerBehavior(TextBehavior, x), textList)) -list(map(lambda x: registerBehavior(MultiTextBehavior, x), ['CATEGORIES', - 'RESOURCES'])) -registerBehavior(SemicolonMultiTextBehavior, 'REQUEST-STATUS') +list(map(lambda x: registerBehavior(MultiTextBehavior, x), ["CATEGORIES", "RESOURCES"])) +registerBehavior(SemicolonMultiTextBehavior, "REQUEST-STATUS") # ------------------------ Serializing helper functions ------------------------ @@ -1614,7 +1690,7 @@ def numToDigits(num, places): if len(s) < places: return ("0" * (places - len(s))) + s elif len(s) > places: - return s[len(s)-places:] + return s[len(s) - places :] else: return s @@ -1633,22 +1709,22 @@ def timedeltaToString(delta): minutes = int((delta.seconds % 3600) / 60) seconds = int(delta.seconds % 60) - output = '' + output = "" if sign == -1: - output += '-' - output += 'P' + output += "-" + output += "P" if days: - output += '{}D'.format(days) + output += "{}D".format(days) if hours or minutes or seconds: - output += 'T' + output += "T" elif not days: # Deal with zero duration - output += 'T0S' + output += "T0S" if hours: - output += '{}H'.format(hours) + output += "{}H".format(hours) if minutes: - output += '{}M'.format(minutes) + output += "{}M".format(minutes) if seconds: - output += '{}S'.format(seconds) + output += "{}S".format(seconds) return output @@ -1657,15 +1733,15 @@ def timeToString(dateOrDateTime): Wraps dateToString and dateTimeToString, returning the results of either based on the type of the argument """ - if hasattr(dateOrDateTime, 'hour'): + if hasattr(dateOrDateTime, "hour"): return dateTimeToString(dateOrDateTime) return dateToString(dateOrDateTime) def dateToString(date): - year = numToDigits(date.year, 4) + year = numToDigits(date.year, 4) month = numToDigits(date.month, 2) - day = numToDigits(date.day, 2) + day = numToDigits(date.day, 2) return year + month + day @@ -1736,12 +1812,12 @@ def stringToDateTime(s, tzinfo=None): minute = int(s[11:13]) second = int(s[13:15]) if len(s) > 15: - if s[15] == 'Z': - tzinfo = getTzid('UTC') - except: + if s[15] == "Z": + tzinfo = getTzid("UTC") + except Exception: raise ParseError("'{0!s}' is not a valid DATE-TIME".format(s)) year = year and year or 2000 - if tzinfo is not None and hasattr(tzinfo,'localize'): # PyTZ case + if tzinfo is not None and hasattr(tzinfo, "localize"): # PyTZ case return tzinfo.localize(datetime.datetime(year, month, day, hour, minute, second)) return datetime.datetime(year, month, day, hour, minute, second, 0, tzinfo) @@ -1751,7 +1827,7 @@ def stringToDateTime(s, tzinfo=None): escapableCharList = '\\;,Nn"' -def stringToTextValues(s, listSeparator=',', charList=None, strict=False): +def stringToTextValues(s, listSeparator=",", charList=None, strict=False): """ Returns list of strings. """ @@ -1777,11 +1853,11 @@ def error(msg): while True: try: charIndex, char = next(charIterator) - except: + except Exception: char = "eof" if state == "read normal": - if char == '\\': + if char == "\\": state = "read escaped char" elif char == listSeparator: state = "read normal" @@ -1797,14 +1873,14 @@ def error(msg): elif state == "read escaped char": if escapableChar(char): state = "read normal" - if char in 'nN': - current.append('\n') + if char in "nN": + current.append("\n") else: current.append(char) else: state = "read normal" # leave unrecognized escaped characters for later passes - current.append('\\' + char) + current.append("\\" + char) elif state == "end": # an end state if len(current) or len(results) == 0: @@ -1824,6 +1900,7 @@ def stringToDurations(s, strict=False): """ Returns list of timedelta objects. """ + def makeTimedelta(sign, week, day, hour, minute, sec): if sign == "-": sign = -1 @@ -1834,8 +1911,7 @@ def makeTimedelta(sign, week, day, hour, minute, sec): hour = int(hour) minute = int(minute) sec = int(sec) - return sign * datetime.timedelta(weeks=week, days=day, hours=hour, - minutes=minute, seconds=sec) + return sign * datetime.timedelta(weeks=week, days=day, hours=hour, minutes=minute, seconds=sec) def error(msg): if strict: @@ -1859,17 +1935,17 @@ def error(msg): while True: try: charIndex, char = next(charIterator) - except: + except Exception: char = "eof" if state == "start": - if char == '+': + if char == "+": state = "start" sign = char - elif char == '-': + elif char == "-": state = "start" sign = char - elif char.upper() == 'P': + elif char.upper() == "P": state = "read field" elif char == "eof": state = "error" @@ -1879,39 +1955,37 @@ def error(msg): current = current + char # update this part when updating "read field" else: state = "error" - error("got unexpected character {0} reading in duration: {1}" - .format(char, s)) + error("got unexpected character {0} reading in duration: {1}".format(char, s)) elif state == "read field": - if (char in string.digits): + if char in string.digits: state = "read field" current = current + char # update part above when updating "read field" - elif char.upper() == 'T': + elif char.upper() == "T": state = "read field" - elif char.upper() == 'W': + elif char.upper() == "W": state = "read field" week = current current = "" - elif char.upper() == 'D': + elif char.upper() == "D": state = "read field" day = current current = "" - elif char.upper() == 'H': + elif char.upper() == "H": state = "read field" hour = current current = "" - elif char.upper() == 'M': + elif char.upper() == "M": state = "read field" minute = current current = "" - elif char.upper() == 'S': + elif char.upper() == "S": state = "read field" sec = current current = "" elif char == ",": state = "start" - durations.append(makeTimedelta(sign, week, day, hour, minute, - sec)) + durations.append(makeTimedelta(sign, week, day, hour, minute, sec)) current = "" sign = None week = None @@ -1926,9 +2000,8 @@ def error(msg): error("got unexpected character reading in duration: " + s) elif state == "end": # an end state - if (sign or week or day or hour or minute or sec): - durations.append(makeTimedelta(sign, week, day, hour, minute, - sec)) + if sign or week or day or hour or minute or sec: + durations.append(makeTimedelta(sign, week, day, hour, minute, sec)) return durations elif state == "error": # an end state @@ -1948,14 +2021,14 @@ def parseDtstart(contentline, allowSignatureMismatch=False): parameter, so rather than failing on these (technically invalid) lines, if allowSignatureMismatch is True, try to parse both varieties. """ - tzinfo = getTzid(getattr(contentline, 'tzid_param', None)) - valueParam = getattr(contentline, 'value_param', 'DATE-TIME').upper() + tzinfo = getTzid(getattr(contentline, "tzid_param", None)) + valueParam = getattr(contentline, "value_param", "DATE-TIME").upper() if valueParam == "DATE": return stringToDate(contentline.value) elif valueParam == "DATE-TIME": try: return stringToDateTime(contentline.value, tzinfo) - except: + except Exception: if allowSignatureMismatch: return stringToDate(contentline.value) else: @@ -1968,15 +2041,16 @@ def stringToPeriod(s, tzinfo=None): valEnd = values[1] if isDuration(valEnd): # period-start = date-time "/" dur-value delta = stringToDurations(valEnd)[0] - return (start, delta) + return start, delta else: - return (start, stringToDateTime(valEnd, tzinfo)) + return start, stringToDateTime(valEnd, tzinfo) def getTransition(transitionTo, year, tzinfo): """ Return the datetime of the transition to/from DST, or None. """ + def firstTransition(iterDates, test): """ Return the last date not matching test, or None if all tests matched. @@ -2010,8 +2084,9 @@ def generateDates(year, month=None, day=None): for hour in hours: yield datetime.datetime(year, month, day, hour) - assert transitionTo in ('daylight', 'standard') - if transitionTo == 'daylight': + assert transitionTo in ("daylight", "standard") + if transitionTo == "daylight": + def test(dt): try: return tzinfo.dst(dt) != zeroDelta @@ -2019,7 +2094,9 @@ def test(dt): return True # entering daylight time except pytz.AmbiguousTimeError: return False # entering standard time - elif transitionTo == 'standard': + + elif transitionTo == "standard": + def test(dt): try: return tzinfo.dst(dt) == zeroDelta @@ -2027,6 +2104,7 @@ def test(dt): return False # entering daylight time except pytz.AmbiguousTimeError: return True # entering standard time + newyear = datetime.datetime(year, 1, 1) monthDt = firstTransition(generateDates(year), test) if monthDt is None: @@ -2038,7 +2116,7 @@ def test(dt): month = monthDt.month day = firstTransition(generateDates(year, month), test).day uncorrected = firstTransition(generateDates(year, month, day), test) - if transitionTo == 'standard': + if transitionTo == "standard": # assuming tzinfo.dst returns a new offset for the first # possible hour, we need to add one hour for the offset change # and another hour because firstTransition returns the hour @@ -2065,7 +2143,7 @@ def dt_test(dt): if not dt_test(datetime.datetime(startYear, 1, 1)): return False for year in range(startYear, endYear): - for transitionTo in 'daylight', 'standard': + for transitionTo in "daylight", "standard": t1 = getTransition(transitionTo, year, tzinfo1) t2 = getTransition(transitionTo, year, tzinfo2) if t1 != t2 or not dt_test(t1): @@ -2074,6 +2152,7 @@ def dt_test(dt): # ------------------- Testing and running functions ---------------------------- -if __name__ == '__main__': +if __name__ == "__main__": import tests + tests._test() diff --git a/vobject/ics_diff.py b/vobject/ics_diff.py index 6d48e3b..3433890 100644 --- a/vobject/ics_diff.py +++ b/vobject/ics_diff.py @@ -11,19 +11,19 @@ def getSortKey(component): def getUID(component): - return component.getChildValue('uid', '') + return component.getChildValue("uid", "") # it's not quite as simple as getUID, need to account for recurrenceID and # sequence def getSequence(component): - sequence = component.getChildValue('sequence', 0) + sequence = component.getChildValue("sequence", 0) return "{0:05d}".format(int(sequence)) def getRecurrenceID(component): - recurrence_id = component.getChildValue('recurrence_id', None) + recurrence_id = component.getChildValue("recurrence_id", None) if recurrence_id is None: - return '0000-00-00' + return "0000-00-00" else: return recurrence_id.isoformat() @@ -42,9 +42,9 @@ def deleteExtraneous(component, ignore_dtstamp=False): for comp in component.components(): deleteExtraneous(comp, ignore_dtstamp) for line in component.lines(): - if 'X-VOBJ-ORIGINAL-TZID' in line.params: - del line.params['X-VOBJ-ORIGINAL-TZID'] - if ignore_dtstamp and hasattr(component, 'dtstamp_list'): + if "X-VOBJ-ORIGINAL-TZID" in line.params: + del line.params["X-VOBJ-ORIGINAL-TZID"] + if ignore_dtstamp and hasattr(component, "dtstamp_list"): del component.dtstamp_list @@ -118,14 +118,12 @@ def processComponentPair(leftComp, rightComp): for key in leftChildKeys: rightList = rightComp.contents.get(key, []) if isinstance(leftComp.contents[key][0], Component): - compDifference = processComponentLists(leftComp.contents[key], - rightList) + compDifference = processComponentLists(leftComp.contents[key], rightList) if len(compDifference) > 0: differentComponents[key] = compDifference elif leftComp.contents[key] != rightList: - differentContentLines.append((leftComp.contents[key], - rightList)) + differentContentLines.append((leftComp.contents[key], rightList)) for key in rightChildKeys: if key not in leftChildKeys: @@ -141,10 +139,10 @@ def processComponentPair(leftComp, rightComp): right = newFromBehavior(leftComp.name) # add a UID, if one existed, despite the fact that they'll always be # the same - uid = leftComp.getChildValue('uid') + uid = leftComp.getChildValue("uid") if uid is not None: - left.add('uid').value = uid - right.add('uid').value = uid + left.add("uid").value = uid + right.add("uid").value = uid for name, childPairList in differentComponents.items(): leftComponents, rightComponents = zip(*childPairList) @@ -165,11 +163,13 @@ def processComponentPair(leftComp, rightComp): return left, right - vevents = processComponentLists(sortByUID(getattr(left, 'vevent_list', [])), - sortByUID(getattr(right, 'vevent_list', []))) + vevents = processComponentLists( + sortByUID(getattr(left, "vevent_list", [])), sortByUID(getattr(right, "vevent_list", [])) + ) - vtodos = processComponentLists(sortByUID(getattr(left, 'vtodo_list', [])), - sortByUID(getattr(right, 'vtodo_list', []))) + vtodos = processComponentLists( + sortByUID(getattr(left, "vtodo_list", [])), sortByUID(getattr(right, "vtodo_list", [])) + ) return vevents + vtodos @@ -198,18 +198,25 @@ def main(): deleteExtraneous(cal2, ignore_dtstamp=ignore_dtstamp) prettyDiff(cal1, cal2) + version = "0.1" def getOptions(): - ##### Configuration options ##### + # ----Configuration options---- # usage = "usage: %prog [options] ics_file1 ics_file2" parser = OptionParser(usage=usage, version=version) parser.set_description("ics_diff will print a comparison of two iCalendar files ") - parser.add_option("-i", "--ignore-dtstamp", dest="ignore", action="store_true", - default=False, help="ignore DTSTAMP lines [default: False]") + parser.add_option( + "-i", + "--ignore-dtstamp", + dest="ignore", + action="store_true", + default=False, + help="ignore DTSTAMP lines [default: False]", + ) (cmdline_options, args) = parser.parse_args() if len(args) < 2: @@ -220,6 +227,7 @@ def getOptions(): return cmdline_options, args + if __name__ == "__main__": try: main() diff --git a/vobject/vcard.py b/vobject/vcard.py index 59c87aa..cb61b25 100644 --- a/vobject/vcard.py +++ b/vobject/vcard.py @@ -3,17 +3,14 @@ import codecs from . import behavior - -from .base import ContentLine, registerBehavior, backslashEscape, basestring, str_ +from .base import ContentLine, backslashEscape, basestring, registerBehavior, str_ from .icalendar import stringToTextValues - # ------------------------ vCard structs --------------------------------------- class Name(object): - def __init__(self, family='', given='', additional='', prefix='', - suffix=''): + def __init__(self, family="", given="", additional="", prefix="", suffix=""): """ Each name attribute can be a string or a list of strings. """ @@ -28,13 +25,11 @@ def toString(val): """ Turn a string or array value into a string. """ - if type(val) in (list, tuple): - return ' '.join(val) - return val + return " ".join(val) if type(val) in (list, tuple) else val def __str__(self): - eng_order = ('prefix', 'given', 'additional', 'family', 'suffix') - out = ' '.join(self.toString(getattr(self, val)) for val in eng_order) + eng_order = ("prefix", "given", "additional", "family", "suffix") + out = " ".join(self.toString(getattr(self, val)) for val in eng_order) return str_(out) def __repr__(self): @@ -42,18 +37,19 @@ def __repr__(self): def __eq__(self, other): try: - return (self.family == other.family and - self.given == other.given and - self.additional == other.additional and - self.prefix == other.prefix and - self.suffix == other.suffix) - except: + return ( + self.family == other.family + and self.given == other.given + and self.additional == other.additional + and self.prefix == other.prefix + and self.suffix == other.suffix + ) + except Exception: return False class Address(object): - def __init__(self, street='', city='', region='', code='', - country='', box='', extended=''): + def __init__(self, street="", city="", region="", code="", country="", box="", extended=""): """ Each name attribute can be a string or a list of strings. """ @@ -66,7 +62,7 @@ def __init__(self, street='', city='', region='', code='', self.country = country @staticmethod - def toString(val, join_char='\n'): + def toString(val, join_char="\n"): """ Turn a string or array value into a string. """ @@ -74,17 +70,15 @@ def toString(val, join_char='\n'): return join_char.join(val) return val - lines = ('box', 'extended', 'street') - one_line = ('city', 'region', 'code') + lines = ("box", "extended", "street") + one_line = ("city", "region", "code") def __str__(self): - lines = '\n'.join(self.toString(getattr(self, val)) - for val in self.lines if getattr(self, val)) - one_line = tuple(self.toString(getattr(self, val), ' ') - for val in self.one_line) + lines = "\n".join(self.toString(getattr(self, val)) for val in self.lines if getattr(self, val)) + one_line = tuple(self.toString(getattr(self, val), " ") for val in self.one_line) lines += "\n{0!s}, {1!s} {2!s}".format(*one_line) if self.country: - lines += '\n' + self.toString(self.country) + lines += "\n" + self.toString(self.country) return lines def __repr__(self): @@ -92,19 +86,22 @@ def __repr__(self): def __eq__(self, other): try: - return (self.box == other.box and - self.extended == other.extended and - self.street == other.street and - self.city == other.city and - self.region == other.region and - self.code == other.code and - self.country == other.country) - except: + return ( + self.box == other.box + and self.extended == other.extended + and self.street == other.street + and self.city == other.city + and self.region == other.region + and self.code == other.code + and self.country == other.country + ) + except Exception: return False # ------------------------ Registered Behavior subclasses ---------------------- + class VCardTextBehavior(behavior.Behavior): """ Provide backslash escape encoding/decoding for single valued properties. @@ -112,8 +109,9 @@ class VCardTextBehavior(behavior.Behavior): TextBehavior also deals with base64 encoding if the ENCODING parameter is explicitly set to BASE64. """ + allowGroup = True - base64string = 'B' + base64string = "B" @classmethod def decode(cls, line): @@ -126,10 +124,10 @@ def decode(cls, line): ENCODING=b """ if line.encoded: - if 'BASE64' in line.singletonparams: - line.singletonparams.remove('BASE64') + if "BASE64" in line.singletonparams: + line.singletonparams.remove("BASE64") line.encoding_param = cls.base64string - encoding = getattr(line, 'encoding_param', None) + encoding = getattr(line, "encoding_param", None) if encoding: if isinstance(line.value, bytes): line.value = codecs.decode(line.value, "base64") @@ -145,10 +143,10 @@ def encode(cls, line): Backslash escape line.value. """ if not line.encoded: - encoding = getattr(line, 'encoding_param', None) + encoding = getattr(line, "encoding_param", None) if encoding and encoding.upper() == cls.base64string: if isinstance(line.value, bytes): - line.value = codecs.encode(line.value, "base64").decode("utf-8").replace('\n', '') + line.value = codecs.encode(line.value, "base64").decode("utf-8").replace("\n", "") else: line.value = codecs.encode(line.value.encode(encoding), "base64").decode("utf-8") else: @@ -165,22 +163,23 @@ class VCard3_0(VCardBehavior): """ vCard 3.0 behavior. """ - name = 'VCARD' - description = 'vCard 3.0, defined in rfc2426' - versionString = '3.0' + + name = "VCARD" + description = "vCard 3.0, defined in rfc2426" + versionString = "3.0" isComponent = True - sortFirst = ('version', 'prodid', 'uid') + sortFirst = ("version", "prodid", "uid") knownChildren = { - 'N': (0, 1, None), # min, max, behaviorRegistry id - 'FN': (1, None, None), - 'VERSION': (1, 1, None), # required, auto-generated - 'PRODID': (0, 1, None), - 'LABEL': (0, None, None), - 'UID': (0, None, None), - 'ADR': (0, None, None), - 'ORG': (0, None, None), - 'PHOTO': (0, None, None), - 'CATEGORIES': (0, None, None) + "N": (0, 1, None), # min, max, behaviorRegistry id + "FN": (1, None, None), + "VERSION": (1, 1, None), # required, auto-generated + "PRODID": (0, 1, None), + "LABEL": (0, None, None), + "UID": (0, None, None), + "ADR": (0, None, None), + "ORG": (0, None, None), + "PHOTO": (0, None, None), + "CATEGORIES": (0, None, None), } @classmethod @@ -191,36 +190,42 @@ def generateImplicitParameters(cls, obj): VTIMEZONEs will need to exist whenever TZID parameters exist or when datetimes with tzinfo exist. """ - if not hasattr(obj, 'version'): - obj.add(ContentLine('VERSION', [], cls.versionString)) + if not hasattr(obj, "version"): + obj.add(ContentLine("VERSION", [], cls.versionString)) + + registerBehavior(VCard3_0, default=True) class FN(VCardTextBehavior): name = "FN" - description = 'Formatted name' + description = "Formatted name" + + registerBehavior(FN) class Label(VCardTextBehavior): name = "Label" - description = 'Formatted address' + description = "Formatted address" + + registerBehavior(Label) wacky_apple_photo_serialize = True -REALLY_LARGE = 1E50 +REALLY_LARGE = 1e50 class Photo(VCardTextBehavior): name = "Photo" - description = 'Photograph' + description = "Photograph" @classmethod def valueRepr(cls, line): return " (BINARY PHOTO DATA at 0x{0!s}) ".format(id(line.value)) @classmethod - def serialize(cls, obj, buf, lineLength, validate, *args, **kwargs): + def serialize(cls, obj, buf, lineLength, validate, *args, **kwargs): """ Apple's Address Book is *really* weird with images, it expects base64 data to have very specific whitespace. It seems Address Book @@ -229,6 +234,8 @@ def serialize(cls, obj, buf, lineLength, validate, *args, **kwargs): if wacky_apple_photo_serialize: lineLength = REALLY_LARGE VCardTextBehavior.serialize(obj, buf, lineLength, validate, *args, **kwargs) + + registerBehavior(Photo) @@ -244,8 +251,7 @@ def splitFields(string): """ Return a list of strings or lists from a Name or Address. """ - return [toListOrString(i) for i in - stringToTextValues(string, listSeparator=';', charList=';')] + return [toListOrString(i) for i in stringToTextValues(string, listSeparator=";", charList=";")] def toList(stringOrList): @@ -266,21 +272,20 @@ def serializeFields(obj, order=None): fields = [backslashEscape(val) for val in obj] else: for field in order: - escapedValueList = [backslashEscape(val) for val in - toList(getattr(obj, field))] - fields.append(','.join(escapedValueList)) - return ';'.join(fields) + escapedValueList = [backslashEscape(val) for val in toList(getattr(obj, field))] + fields.append(",".join(escapedValueList)) + return ";".join(fields) -NAME_ORDER = ('family', 'given', 'additional', 'prefix', 'suffix') -ADDRESS_ORDER = ('box', 'extended', 'street', 'city', 'region', 'code', - 'country') +NAME_ORDER = ("family", "given", "additional", "prefix", "suffix") +ADDRESS_ORDER = ("box", "extended", "street", "city", "region", "code", "country") class NameBehavior(VCardBehavior): """ A structured name. """ + hasNative = True @staticmethod @@ -302,13 +307,16 @@ def transformFromNative(obj): obj.isNative = False obj.value = serializeFields(obj.value, NAME_ORDER) return obj -registerBehavior(NameBehavior, 'N') + + +registerBehavior(NameBehavior, "N") class AddressBehavior(VCardBehavior): """ A structured address. """ + hasNative = True @staticmethod @@ -330,13 +338,16 @@ def transformFromNative(obj): obj.isNative = False obj.value = serializeFields(obj.value, ADDRESS_ORDER) return obj -registerBehavior(AddressBehavior, 'ADR') + + +registerBehavior(AddressBehavior, "ADR") class OrgBehavior(VCardBehavior): """ A list of organization values and sub-organization values. """ + hasNative = True @staticmethod @@ -360,4 +371,6 @@ def transformFromNative(obj): obj.isNative = False obj.value = serializeFields(obj.value) return obj -registerBehavior(OrgBehavior, 'ORG') + + +registerBehavior(OrgBehavior, "ORG") diff --git a/vobject/win32tz.py b/vobject/win32tz.py index 5c2db66..242a38d 100644 --- a/vobject/win32tz.py +++ b/vobject/win32tz.py @@ -1,23 +1,19 @@ -import _winreg -import struct import datetime +import struct + +import _winreg handle = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) -tzparent = _winreg.OpenKey(handle, - "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones") +tzparent = _winreg.OpenKey(handle, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones") parentsize = _winreg.QueryInfoKey(tzparent)[0] -localkey = _winreg.OpenKey(handle, - "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation") +localkey = _winreg.OpenKey(handle, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation") WEEKS = datetime.timedelta(7) def list_timezones(): """Return a list of all time zones known to the system.""" - l = [] - for i in xrange(parentsize): - l.append(_winreg.EnumKey(tzparent, i)) - return l + return [_winreg.EnumKey(tzparent, i) for i in range(parentsize)] class win32tz(datetime.tzinfo): @@ -61,12 +57,10 @@ def tzname(self, dt): def _isdst(self, dt): dat = self.data - dston = pickNthWeekday(dt.year, dat.dstmonth, dat.dstdayofweek, - dat.dsthour, dat.dstminute, dat.dstweeknumber) - dstoff = pickNthWeekday(dt.year, dat.stdmonth, dat.stddayofweek, - dat.stdhour, dat.stdminute, dat.stdweeknumber) + dston = pickNthWeekday(dt.year, dat.dstmonth, dat.dstdayofweek, dat.dsthour, dat.dstminute, dat.dstweeknumber) + dstoff = pickNthWeekday(dt.year, dat.stdmonth, dat.stddayofweek, dat.stdhour, dat.stdminute, dat.stdweeknumber) if dston < dstoff: - return (dston <= dt.replace(tzinfo=None) < dstoff) + return dston <= dt.replace(tzinfo=None) < dstoff else: return not (dstoff <= dt.replace(tzinfo=None) < dston) @@ -76,10 +70,9 @@ def __repr__(self): def pickNthWeekday(year, month, dayofweek, hour, minute, whichweek): """dayofweek == 0 means Sunday, whichweek > 4 means last instance""" - first = datetime.datetime(year=year, month=month, hour=hour, minute=minute, - day=1) + first = datetime.datetime(year=year, month=month, hour=hour, minute=minute, day=1) weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7 + 1)) - for n in xrange(whichweek - 1, -1, -1): + for n in range(whichweek - 1, -1, -1): dt = weekdayone + n * WEEKS if dt.month == month: return dt @@ -92,12 +85,12 @@ def __init__(self, path): """Load path, or if path is empty, load local time.""" if path: keydict = valuesToDict(_winreg.OpenKey(tzparent, path)) - self.display = keydict['Display'] - self.dstname = keydict['Dlt'] - self.stdname = keydict['Std'] + self.display = keydict["Display"] + self.dstname = keydict["Dlt"] + self.stdname = keydict["Std"] - #see http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm - tup = struct.unpack('=3l16h', keydict['TZI']) + # see http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm + tup = struct.unpack("=3l16h", keydict["TZI"]) self.stdoffset = -tup[0] - tup[1] # Bias + StandardBias * -1 self.dstoffset = self.stdoffset - tup[2] # + DaylightBias * -1 @@ -118,17 +111,17 @@ def __init__(self, path): else: keydict = valuesToDict(localkey) - self.stdname = keydict['StandardName'] - self.dstname = keydict['DaylightName'] + self.stdname = keydict["StandardName"] + self.dstname = keydict["DaylightName"] sourcekey = _winreg.OpenKey(tzparent, self.stdname) - self.display = valuesToDict(sourcekey)['Display'] + self.display = valuesToDict(sourcekey)["Display"] - self.stdoffset = -keydict['Bias'] - keydict['StandardBias'] - self.dstoffset = self.stdoffset - keydict['DaylightBias'] + self.stdoffset = -keydict["Bias"] - keydict["StandardBias"] + self.dstoffset = self.stdoffset - keydict["DaylightBias"] - #see http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm - tup = struct.unpack('=8h', keydict['StandardStart']) + # see http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm + tup = struct.unpack("=8h", keydict["StandardStart"]) offset = 0 self.stdmonth = tup[1 + offset] @@ -137,7 +130,7 @@ def __init__(self, path): self.stdhour = tup[4 + offset] self.stdminute = tup[5 + offset] - tup = struct.unpack('=8h', keydict['DaylightStart']) + tup = struct.unpack("=8h", keydict["DaylightStart"]) self.dstmonth = tup[1 + offset] self.dstdayofweek = tup[2 + offset] # Sunday=0 self.dstweeknumber = tup[3 + offset] # Last = 5 @@ -147,16 +140,17 @@ def __init__(self, path): def valuesToDict(key): """Convert a registry key's values to a dictionary.""" - d = {} size = _winreg.QueryInfoKey(key)[1] - for i in xrange(size): - d[_winreg.EnumValue(key, i)[0]] = _winreg.EnumValue(key, i)[1] - return d + return {_winreg.EnumValue(key, i)[0]: _winreg.EnumValue(key, i)[1] for i in range(size)} def _test(): - import win32tz, doctest - doctest.testmod(win32tz, verbose=0) + import doctest + + import win32tz + + doctest.testmod(win32tz, verbose=False) + -if __name__ == '__main__': +if __name__ == "__main__": _test() From 2630c9f46c7a3ca460029d70f455fec0b6c97826 Mon Sep 17 00:00:00 2001 From: rsb-23 <57601627+rsb-23@users.noreply.github.com> Date: Mon, 1 Apr 2024 22:52:14 +0530 Subject: [PATCH 2/2] All configs moved to toml - lint changes by tools --- .pre-commit-config.yaml | 6 ++++-- CONTRIBUTING.md | 1 - pyproject.toml | 15 +++++++++++++++ setup.cfg | 9 --------- test_files/ms_tzid.ics | 2 +- test_files/ruby_rrule.ics | 2 +- 6 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.cfg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 25ee2cd..b2461ea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,10 +5,10 @@ repos: - id: isort - repo: https://github.com/psf/black - rev: 24.2.0 + rev: 24.3.0 hooks: - id: black - args: [-l, "120", -C] + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: @@ -16,7 +16,9 @@ repos: - id: end-of-file-fixer - id: no-commit-to-branch args: [-b, main, -b, master] + - repo: https://github.com/PyCQA/flake8 rev: 7.0.0 hooks: - id: flake8 + additional_dependencies: [flake8-pyproject] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f2e68e..5a93491 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,4 +34,3 @@ releases. With the possible exception of major releases, all contributions must maintain the existing API's syntax and semantics. - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..377dbc2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[tool.black] +target-version = ["py37", "py38", "py39", "py310", "py311"] +line-length = 120 +skip-magic-trailing-comma = true + +[tool.flake8] +max-line-length = 120 +ignore = ["E203", "E266", "E501", "W503"] +exclude = [".git", "__pycache__", "venv"] +per-file-ignores = ["*/__init__.py: F401"] + +[tool.isort] +profile = "black" +line_length = 120 +multi_line_output = 3 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 66e5a12..0000000 --- a/setup.cfg +++ /dev/null @@ -1,9 +0,0 @@ -[flake8] -max-line-length = 120 -ignore = E203, E266, E501, W503 -exclude = .git, __pycache__, venv -per-file-ignores = */__init__.py: F401 - -[isort] -profile = black -multi_line_output = 3 diff --git a/test_files/ms_tzid.ics b/test_files/ms_tzid.ics index 0db2c5c..3af37ed 100644 --- a/test_files/ms_tzid.ics +++ b/test_files/ms_tzid.ics @@ -36,4 +36,4 @@ BEGIN:VEVENT UID:CommaTest DTSTART;TZID="Canberra, Melbourne, Sydney":20080530T150000 END:VEVENT -END:VCALENDAR \ No newline at end of file +END:VCALENDAR diff --git a/test_files/ruby_rrule.ics b/test_files/ruby_rrule.ics index 6999513..7ce44e8 100644 --- a/test_files/ruby_rrule.ics +++ b/test_files/ruby_rrule.ics @@ -13,4 +13,4 @@ SUMMARY:Something DTSTART:20030101T070000 DTSTAMP:20080529T152100 END:VEVENT -END:VCALENDAR \ No newline at end of file +END:VCALENDAR