From ca9650e65022603b235f83af18d8326b6f086432 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 29 Jan 2025 06:51:53 +0000 Subject: [PATCH 1/5] Replace (most) uses of string.format with f-strings. The exceptions are calls to logging.* functions, where parameters should be passed in *args, so that substitution isn't performed unless the message needs to be emitted; and, a series of uses in constructing parsing regexps, where the named lookup is arguably clearer. --- vobject/base.py | 53 ++++++++++++++++++++++---------------------- vobject/behavior.py | 12 ++++------ vobject/hcalendar.py | 8 ++----- vobject/icalendar.py | 49 +++++++++++++++++----------------------- vobject/ics_diff.py | 4 ++-- vobject/vcard.py | 8 +++---- 6 files changed, 59 insertions(+), 75 deletions(-) diff --git a/vobject/base.py b/vobject/base.py index fe7f1ea..11e6654 100644 --- a/vobject/base.py +++ b/vobject/base.py @@ -156,9 +156,10 @@ def transformToNative(self): e.lineNumber = lineNumber raise - msg = "In transformToNative, unhandled exception on line {0}: {1}: {2}" - msg = msg.format(lineNumber, sys.exc_info()[0], sys.exc_info()[1]) - msg = msg + " (" + str(self_orig) + ")" + msg = ( + f"In transformToNative, unhandled exception on line {lineNumber}: {sys.exc_info()[0]}:" + f" {sys.exc_info()[1]} ({self_orig})" + ) raise ParseError(msg, lineNumber) def transformFromNative(self): @@ -185,8 +186,10 @@ def transformFromNative(self): e.lineNumber = lineNumber raise - msg = "In transformFromNative, unhandled exception on line {0} {1}: {2}" - msg = msg.format(lineNumber, sys.exc_info()[0], sys.exc_info()[1]) + msg = ( + f"In transformFromNative, unhandled exception on line {lineNumber} {sys.exc_info()[0]}:" + f" {sys.exc_info()[1]}" + ) raise NativeError(msg, lineNumber) else: return self @@ -208,11 +211,11 @@ def serialize(self, buf=None, lineLength=75, validate=True, behavior=None, *args if behavior: if DEBUG: - logger.debug("serializing {0!s} with behavior {1!s}".format(self.name, behavior)) + logger.debug("serializing %s with behavior %s", self.name, self.behavior) return behavior.serialize(self, buf, lineLength, validate, *args, **kwargs) else: if DEBUG: - logger.debug("serializing {0!s} without behavior".format(self.name)) + logger.debug("serializing %s without behavior", self.name) return defaultSerialize(self, buf, lineLength) @@ -385,15 +388,15 @@ def valueRepr(self): def __str__(self): try: - return "<{0}{1}{2}>".format(self.name, self.params, self.valueRepr()) + return f"<{self.name}{self.params}{self.valueRepr()}>" except UnicodeEncodeError: - return "<{0}{1}{2}>".format(self.name, self.params, self.valueRepr().encode("utf-8")) + return f"<{self.name}{self.params}{self.valueRepr().encode('utf-8')}>" def __repr__(self): return self.__str__() def __unicode__(self): - return "<{0}{1}{2}>".format(self.name, self.params, self.valueRepr()) + return f"<{self.name}{self.params}{self.valueRepr()}>" def prettyPrint(self, level=0, tabwidth=3): pre = " " * level * tabwidth @@ -642,9 +645,9 @@ def transformChildrenFromNative(self, clearBehavior=True): def __str__(self): if self.name: - return "<{0}| {1}>".format(self.name, self.getSortedChildren()) + return f"<{self.name}| {self.getSortedChildren()}>" else: - return "<*unnamed*| {0}>".format(self.getSortedChildren()) + return f"<*unnamed*| {self.getSortedChildren()}>" def __repr__(self): return self.__str__() @@ -665,7 +668,7 @@ def __init__(self, msg, lineNumber=None): def __str__(self): if hasattr(self, "lineNumber"): - return "At line {0!s}: {1!s}".format(self.lineNumber, self.msg) + return f"At line {self.lineNumber}: {self.msg}" else: return repr(self.msg) @@ -783,7 +786,7 @@ def parseLine(line, lineNumber=None): """ match = line_re.match(line) if match is None: - raise ParseError("Failed to parse line: {0!s}".format(line), lineNumber) + raise ParseError(f"Failed to parse line: {line}", lineNumber) # Underscores are replaced with dash to work around Lotus Notes return ( match.group("name").replace("_", "-"), @@ -975,12 +978,12 @@ def defaultSerialize(obj, buf, lineLength): else: groupString = obj.group + "." if obj.useBegin: - foldOneLine(outbuf, "{0}BEGIN:{1}".format(groupString, obj.name), lineLength) + foldOneLine(outbuf, f"{groupString}BEGIN:{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, f"{groupString}END:{obj.name}", lineLength) elif isinstance(obj, ContentLine): startedEncoded = obj.encoded @@ -996,13 +999,13 @@ def defaultSerialize(obj, buf, lineLength): for key in keys: paramstr = ",".join(dquoteEscape(p) for p in obj.params[key]) try: - s.write(";{0}={1}".format(key, paramstr)) + s.write(f";{key}={paramstr}") except (UnicodeDecodeError, UnicodeEncodeError): - s.write(";{0}={1}".format(key, paramstr.encode("utf-8"))) + s.write(f";{key}={paramstr.encode('utf-8')}") try: - s.write(":{0}".format(obj.value)) + s.write(f":{obj.value}") except (UnicodeDecodeError, UnicodeEncodeError): - s.write(":{0}".format(obj.value.encode("utf-8"))) + s.write(f":{obj.value.encode('utf-8')}") if obj.behavior and not startedEncoded: obj.behavior.decode(obj) foldOneLine(outbuf, s.getvalue(), lineLength) @@ -1082,8 +1085,7 @@ def readComponents(streamOrString, validate=False, transform=True, ignoreUnreada stack.top().setProfile(vline.value) elif vline.name == "END": if len(stack) == 0: - err = "Attempted to end the {0} component but it was never opened" - raise ParseError(err.format(vline.value), n) + raise ParseError(f"Attempted to end the {vline.value} component but it was never opened", n) if vline.value.upper() == stack.topName(): # START matches END if len(stack) == 1: @@ -1102,15 +1104,14 @@ def readComponents(streamOrString, validate=False, transform=True, ignoreUnreada else: stack.modifyTop(stack.pop()) else: - err = "{0} component wasn't closed" - raise ParseError(err.format(stack.topName()), n) + raise ParseError(f"{stack.topName()} component wasn't closed", n) else: stack.modifyTop(vline) # not a START or END line if stack.top(): 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(f"Component {stack.topName()} was never closed", n) yield stack.pop() except ParseError as e: @@ -1173,7 +1174,7 @@ def newFromBehavior(name, id_=None): name = name.upper() behavior = getBehavior(name, id_) if behavior is None: - raise VObjectError("No behavior found named {0!s}".format(name)) + raise VObjectError(f"No behavior found named {name}") if behavior.isComponent: obj = Component(name) else: diff --git a/vobject/behavior.py b/vobject/behavior.py index 3da25d3..d0e7556 100644 --- a/vobject/behavior.py +++ b/vobject/behavior.py @@ -76,8 +76,7 @@ def validate(cls, obj, raiseException=False, complainUnrecognized=False): """ if not cls.allowGroup and obj.group is not None: - err = "{0} has a group, but this object doesn't support groups".format(obj) - raise base.VObjectError(err) + raise base.VObjectError(f"{obj} has a group, but this object doesn't support groups") if isinstance(obj, base.ContentLine): return cls.lineValidate(obj, raiseException, complainUnrecognized) elif isinstance(obj, base.Component): @@ -90,18 +89,15 @@ def validate(cls, obj, raiseException=False, complainUnrecognized=False): for key, val in cls.knownChildren.items(): 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(f"{cls.name} components must contain at least {val[0]} {key}") return False if val[1] and count.get(key, 0) > val[1]: if raiseException: - m = "{0} components cannot contain more than {1} {2}" - raise base.ValidateError(m.format(cls.name, val[1], key)) + raise base.ValidateError(f"{cls.name} components cannot contain more than {val[1]} {key}") return False return True else: - err = "{0} is not a Component or Contentline".format(obj) - raise base.VObjectError(err) + raise base.VObjectError(f"{obj} is not a Component or Contentline") @classmethod def lineValidate(cls, line, raiseException, complainUnrecognized): diff --git a/vobject/hcalendar.py b/vobject/hcalendar.py index 47f1c40..aa3fdb6 100644 --- a/vobject/hcalendar.py +++ b/vobject/hcalendar.py @@ -88,9 +88,7 @@ def out(s): # TODO: Spec says we should handle when dtstart isn't included out( - '{1!s}\r\n'.format( - dtstart.strftime(machine), dtstart.strftime(timeformat) - ) + f'{dtstart.strftime(timeformat)}\r\n' ) # DTEND @@ -108,9 +106,7 @@ def out(s): human = dtend - timedelta(days=1) out( - '- {1!s}\r\n'.format( - dtend.strftime(machine), human.strftime(timeformat) - ) + f'- {human.strftime(timeformat)}\r\n' ) # LOCATION diff --git a/vobject/icalendar.py b/vobject/icalendar.py index 2b30870..0e05b13 100644 --- a/vobject/icalendar.py +++ b/vobject/icalendar.py @@ -330,7 +330,7 @@ def fromLastWeek(dt): endString = ";UNTIL=" + dateTimeToString(endDate) else: endString = "" - new_rule = "FREQ=YEARLY{0!s};BYMONTH={1!s}{2!s}".format(dayString, rule["month"], endString) + new_rule = f"FREQ=YEARLY{dayString};BYMONTH={rule['month']}{endString}" comp.add("rrule").value = new_rule @@ -367,10 +367,10 @@ def pickTzid(tzinfo, allowUTC=False): 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(f"Unable to guess TZID for tzinfo {tzinfo}") def __str__(self): - return "".format(getattr(self, "tzid", "No TZID")) + return f"" def __repr__(self): return self.__str__() @@ -751,7 +751,7 @@ def generateImplicitParameters(obj): 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", [], f"{now} - {rand}@{host}")) if not hasattr(obj, "dtstamp"): now = datetime.datetime.now(utc) @@ -1042,7 +1042,7 @@ def serialize(cls, obj, buf, lineLength, validate=True, *args, **kwargs): else: groupString = obj.group + "." if obj.useBegin: - foldOneLine(outbuf, "{0}BEGIN:{1}".format(groupString, obj.name), lineLength) + foldOneLine(outbuf, f"{groupString}BEGIN:{obj.name}", lineLength) try: first_props = [ @@ -1075,7 +1075,7 @@ def serialize(cls, obj, buf, lineLength, validate=True, *args, **kwargs): # 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, "{groupString}END:{obj.name}", lineLength) out = buf or outbuf.getvalue() if undoTransform: obj.transformToNative() @@ -1720,21 +1720,21 @@ def timedeltaToString(delta): output += "-" output += "P" if days: - output += "{}D".format(days) + output += f"{days}D" if hours or minutes or seconds: output += "T" elif not days: # Deal with zero duration output += "T0S" if hours: - output += "{}H".format(hours) + output += f"{hours}H" if minutes: - output += "{}M".format(minutes) + output += f"{minutes}M" if seconds: - output += "{}S".format(seconds) + output += f"{seconds}S" return output -def timeToString(dateOrDateTime): +def timeToString(dateOrDateTime) -> str: """ Wraps dateToString and dateTimeToString, returning the results of either based on the type of the argument @@ -1744,28 +1744,19 @@ def timeToString(dateOrDateTime): return dateToString(dateOrDateTime) -def dateToString(date): - year = numToDigits(date.year, 4) - month = numToDigits(date.month, 2) - day = numToDigits(date.day, 2) - return year + month + day +def dateToString(date) -> str: + return f"{date.year:04d}{date.month:02d}{date.day:02d}" -def dateTimeToString(dateTime, convertToUTC=False): +def dateTimeToString(dateTime, convertToUTC=False) -> str: """ Ignore tzinfo unless convertToUTC. Output string. """ if dateTime.tzinfo and convertToUTC: dateTime = dateTime.astimezone(utc) - datestr = "{0}{1}{2}T{3}{4}{5}".format( - numToDigits(dateTime.year, 4), - numToDigits(dateTime.month, 2), - numToDigits(dateTime.day, 2), - numToDigits(dateTime.hour, 2), - numToDigits(dateTime.minute, 2), - numToDigits(dateTime.second, 2), - ) + datestr = f"{dateToString(dateTime)}T{dateTime.hour:02d}{dateTime.minute:02d}{dateTime.second:02d}" + if tzinfo_eq(dateTime.tzinfo, utc): datestr += "Z" return datestr @@ -1823,7 +1814,7 @@ def stringToDateTime(s, tzinfo=None, strict=False): if len(s) > 15 and s[15] == "Z": tzinfo = getTzid("UTC") except TypeError: - raise ParseError("'{0!s}' is not a valid DATE-TIME".format(s)) + raise ParseError(f"'{s}' is not a valid DATE-TIME") year = year or 2000 if tzinfo is not None and hasattr(tzinfo, "localize"): # Cater for pytz tzinfo instanes return tzinfo.localize(datetime.datetime(year, month, day, hour, minute, second)) @@ -1900,7 +1891,7 @@ def error(msg): else: state = "error" - error("unknown state: '{0!s}' reached in {1!s}".format(state, s)) + error("unknown state: '{state}' reached in {s}") def stringToDurations(s, strict=False): @@ -1961,7 +1952,7 @@ 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 {char} reading in duration: {s}") elif state == "read field": if char in string.digits: @@ -2016,7 +2007,7 @@ def error(msg): else: state = "error" - error("unknown state: '{0!s}' reached in {1!s}".format(state, s)) + error("unknown state: '{state}' reached in {s}") def parseDtstart(contentline, allowSignatureMismatch=False): diff --git a/vobject/ics_diff.py b/vobject/ics_diff.py index 8e1ec27..b7099e9 100644 --- a/vobject/ics_diff.py +++ b/vobject/ics_diff.py @@ -13,9 +13,9 @@ def getUID(component): # it's not quite as simple as getUID, need to account for recurrenceID and sequence - def getSequence(component): + def getSequence(component) -> str: sequence = component.getChildValue("sequence", 0) - return "{0:05d}".format(int(sequence)) + return f"{int(sequence):05d}" def getRecurrenceID(component): recurrence_id = component.getChildValue("recurrence_id", None) diff --git a/vobject/vcard.py b/vobject/vcard.py index 922cea2..7ac402f 100644 --- a/vobject/vcard.py +++ b/vobject/vcard.py @@ -35,7 +35,7 @@ def __str__(self): return str_(out) def __repr__(self): - return "".format(self.__str__()) + return f"" def __eq__(self, other): try: @@ -78,13 +78,13 @@ def toString(val, join_char="\n"): 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{0!s}, {1!s} {2!s}".format(*one_line) + lines += f"\n{one_line[0]}, {one_line[1]} {one_line[2]}" if self.country: lines += "\n" + self.toString(self.country) return lines def __repr__(self): - return "".format(self) + return "" def __eq__(self, other): try: @@ -234,7 +234,7 @@ class Photo(VCardTextBehavior): @classmethod def valueRepr(cls, line): - return " (BINARY PHOTO DATA at 0x{0!s}) ".format(id(line.value)) + return f" (BINARY PHOTO DATA at 0x{id(line.value)}) " @classmethod def serialize(cls, obj, buf, lineLength, validate, *args, **kwargs): From 159f12d75370424a3f7f5bdb8f74f6b1a3e7af9d Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 29 Jan 2025 12:07:46 +0000 Subject: [PATCH 2/5] Remove uses of string.format from the parsing regexes. These looked a bit messy to start with, but the reality was not so bad. I think the resulting code is substantially more clear too. --- tests/test_icalendar.py | 19 ++++++--- vobject/base.py | 89 +++++++++++++++-------------------------- vobject/icalendar.py | 2 +- 3 files changed, 46 insertions(+), 64 deletions(-) diff --git a/tests/test_icalendar.py b/tests/test_icalendar.py index 0234f8f..9eec7b9 100644 --- a/tests/test_icalendar.py +++ b/tests/test_icalendar.py @@ -282,12 +282,19 @@ def test_regexes(): """ Test regex patterns """ - assert re.findall(vobject.base.patterns["name"], "12foo-bar:yay") == ["12foo-bar", "yay"] - assert re.findall(vobject.base.patterns["safe_char"], 'a;b"*,cd') == ["a", "b", "*", "c", "d"] - assert re.findall(vobject.base.patterns["qsafe_char"], 'a;b"*,cd') == ["a", ";", "b", "*", ",", "c", "d"] - assert re.findall( - vobject.base.patterns["param_value"], '"quoted";not-quoted;start"after-illegal-quote', re.VERBOSE - ) == ['"quoted"', "", "not-quoted", "", "start", "", "after-illegal-quote", ""] + assert re.findall(vobject.base.P_NAME, "12foo-bar:yay") == ["12foo-bar", "yay"] + assert re.findall(vobject.base.P_SAFE_CHAR, 'a;b"*,cd') == ["a", "b", "*", "c", "d"] + assert re.findall(vobject.base.P_QSAFE_CHAR, 'a;b"*,cd') == ["a", ";", "b", "*", ",", "c", "d"] + assert re.findall(vobject.base.P_PARAM_VALUE, '"quoted";not-quoted;start"after-illegal-quote', re.VERBOSE) == [ + '"quoted"', + "", + "not-quoted", + "", + "start", + "", + "after-illegal-quote", + "", + ] match = vobject.base.line_re.match('TEST;ALTREP="http://www.wiz.org":value:;"') assert match.group("value") == 'value:;"' diff --git a/vobject/base.py b/vobject/base.py index 11e6654..0287e80 100644 --- a/vobject/base.py +++ b/vobject/base.py @@ -689,76 +689,55 @@ 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"] = '[^"]' +P_NAME = "[a-zA-Z0-9_-]+" # 1*(ALPHA / DIGIT / "-") +P_SAFE_CHAR = '[^";:,]' +P_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) - +P_PARAM_VALUE = f' "{P_QSAFE_CHAR} * " | {P_SAFE_CHAR} * ' # get a tuple of two elements, one will be empty, the other will have the value -patterns["param_value_grouped"] = ( - """ -" ( {qsafe_char!s} * )" | ( {safe_char!s} + ) -""".format( - **patterns - ) -) +P_PARAM_VALUE_GROUPED = f' " ( {P_QSAFE_CHAR} * )" | ( {P_SAFE_CHAR} + ) ' # get a parameter and its values, without any saved groups -patterns["param"] = ( - r""" -; (?: {name!s} ) # parameter name +P_PARAM = rf""" +; (?: {P_NAME} ) # parameter name (?: - (?: = (?: {param_value!s} ) )? # 0 or more parameter values, multiple - (?: , (?: {param_value!s} ) )* # parameters are comma separated + (?: = (?: {P_PARAM_VALUE} ) )? # 0 or more parameter values, multiple + (?: , (?: {P_PARAM_VALUE} ) )* # parameters are comma separated )* -""".format( - **patterns - ) -) +""" # get a parameter, saving groups for name and value (value still needs parsing) -patterns["params_grouped"] = ( - r""" -; ( {name!s} ) +P_PARAMS_GROUPED = rf""" +; ( {P_NAME} ) (?: = ( - (?: (?: {param_value!s} ) )? # 0 or more parameter values, multiple - (?: , (?: {param_value!s} ) )* # parameters are comma separated + (?: (?: {P_PARAM_VALUE} ) )? # 0 or more parameter values, multiple + (?: , (?: {P_PARAM_VALUE} ) )* # parameters are comma separated ) )? -""".format( - **patterns - ) -) +""" # get a full content line, break it up into group, name, parameters, and value -patterns["line"] = ( - r""" -^ ((?P {name!s})\.)?(?P {name!s}) # name group - (?P ;?(?: {param!s} )* ) # params group (may be empty) +P_LINE = rf""" +^ ((?P {P_NAME})\.)?(?P {P_NAME}) # name group + (?P ;?(?: {P_PARAM} )* ) # params group (may be empty) : (?P .* )$ # value group -""".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) +param_values_re = re.compile(P_PARAM_VALUE_GROUPED, re.VERBOSE) +params_re = re.compile(P_PARAMS_GROUPED, re.VERBOSE) +line_re = re.compile(P_LINE, re.DOTALL | re.VERBOSE) begin_re = re.compile("BEGIN", re.IGNORECASE) @@ -798,23 +777,19 @@ def parseLine(line, lineNumber=None): # logical line regular expressions -patterns["lineend"] = r"(?:\r\n|\r|\n|$)" -patterns["wrap"] = r"{lineend!s} [\t ]".format(**patterns) -patterns["logicallines"] = ( - r""" +P_LINEEND = r"(?:\r\n|\r|\n|$)" +P_WRAP = rf"{P_LINEEND} [\t ]" +P_LOGICALLINES = rf""" ( - (?: [^\r\n] | {wrap!s} )* - {lineend!s} -) -""".format( - **patterns - ) + (?: [^\r\n] | {P_WRAP} )* + {P_LINEEND} ) +""" -patterns["wraporend"] = r"({wrap!s} | {lineend!s} )".format(**patterns) +P_WRAPOREND = rf"({P_WRAP} | {P_LINEEND} )" -wrap_re = re.compile(patterns["wraporend"], re.VERBOSE) -logical_lines_re = re.compile(patterns["logicallines"], re.VERBOSE) +wrap_re = re.compile(P_WRAPOREND, re.VERBOSE) +logical_lines_re = re.compile(P_LOGICALLINES, re.VERBOSE) testLines = """ Line 0 text diff --git a/vobject/icalendar.py b/vobject/icalendar.py index 0e05b13..03a6595 100644 --- a/vobject/icalendar.py +++ b/vobject/icalendar.py @@ -46,7 +46,7 @@ class NonExistentTimeError(Exception): DATENAMES = ("rdate", "exdate") RULENAMES = ("exrule", "rrule") DATESANDRULES = ("exrule", "rrule", "rdate", "exdate") -PRODID = "-//PYVOBJECT//NONSGML Version %s//EN" % VERSION +PRODID = f"-//PYVOBJECT//NONSGML Version {VERSION}//EN" WEEKDAYS = "MO", "TU", "WE", "TH", "FR", "SA", "SU" FREQUENCIES = ("YEARLY", "MONTHLY", "WEEKLY", "DAILY", "HOURLY", "MINUTELY", "SECONDLY") From 474ee61b60715254cd13f174afe8736100509398 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 29 Jan 2025 12:09:34 +0000 Subject: [PATCH 3/5] Remove suppression of f-string conversion warnings. --- .pylintrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 22c0a2e..6aa5a3a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -29,7 +29,6 @@ disable= unspecified-encoding, unused-argument, # C - consider-using-f-string, import-outside-toplevel, invalid-name, missing-docstring, From 56d1aa763422300f8e393ff41df5d39d50cdf9ab Mon Sep 17 00:00:00 2001 From: David Arnold Date: Thu, 30 Jan 2025 12:42:22 +0000 Subject: [PATCH 4/5] Fix missing f-prefix. Caught by unit tests. --- vobject/icalendar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vobject/icalendar.py b/vobject/icalendar.py index 03a6595..6ce5502 100644 --- a/vobject/icalendar.py +++ b/vobject/icalendar.py @@ -1075,7 +1075,7 @@ def serialize(cls, obj, buf, lineLength, validate=True, *args, **kwargs): # validate is recursive, we only need to validate once child.serialize(outbuf, lineLength, validate=False) if obj.useBegin: - foldOneLine(outbuf, "{groupString}END:{obj.name}", lineLength) + foldOneLine(outbuf, f"{groupString}END:{obj.name}", lineLength) out = buf or outbuf.getvalue() if undoTransform: obj.transformToNative() From 81064877ccb0e1417f6deae69a8592abc58b2efc Mon Sep 17 00:00:00 2001 From: David Arnold Date: Fri, 31 Jan 2025 13:03:52 +0000 Subject: [PATCH 5/5] Format hack for black. --- tests/test_icalendar.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/test_icalendar.py b/tests/test_icalendar.py index 9eec7b9..e3fd81a 100644 --- a/tests/test_icalendar.py +++ b/tests/test_icalendar.py @@ -285,16 +285,9 @@ def test_regexes(): assert re.findall(vobject.base.P_NAME, "12foo-bar:yay") == ["12foo-bar", "yay"] assert re.findall(vobject.base.P_SAFE_CHAR, 'a;b"*,cd') == ["a", "b", "*", "c", "d"] assert re.findall(vobject.base.P_QSAFE_CHAR, 'a;b"*,cd') == ["a", ";", "b", "*", ",", "c", "d"] - assert re.findall(vobject.base.P_PARAM_VALUE, '"quoted";not-quoted;start"after-illegal-quote', re.VERBOSE) == [ - '"quoted"', - "", - "not-quoted", - "", - "start", - "", - "after-illegal-quote", - "", - ] + assert re.findall( + vobject.base.P_PARAM_VALUE, '"quoted";not-quoted;start"after-illegal-quote', re.VERBOSE # black hack + ) == ['"quoted"', "", "not-quoted", "", "start", "", "after-illegal-quote", ""] match = vobject.base.line_re.match('TEST;ALTREP="http://www.wiz.org":value:;"') assert match.group("value") == 'value:;"'