Skip to content

Commit

Permalink
Merge pull request py-vobject#103 from py-vobject/98-remove-python2-r…
Browse files Browse the repository at this point in the history
…emnants

Remove python2 remnants
  • Loading branch information
da4089 authored Jan 31, 2025
2 parents 1008f69 + 8106487 commit 2094c71
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 138 deletions.
1 change: 0 additions & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ disable=
unspecified-encoding,
unused-argument,
# C
consider-using-f-string,
import-outside-toplevel,
invalid-name,
missing-docstring,
Expand Down
8 changes: 4 additions & 4 deletions tests/test_icalendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,11 @@ 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.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.patterns["param_value"], '"quoted";not-quoted;start"after-illegal-quote', re.VERBOSE
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:;"')
Expand Down
142 changes: 59 additions & 83 deletions vobject/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand All @@ -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)


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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__()
Expand All @@ -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)

Expand All @@ -686,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<group> {name!s})\.)?(?P<name> {name!s}) # name group
(?P<params> ;?(?: {param!s} )* ) # params group (may be empty)
P_LINE = rf"""
^ ((?P<group> {P_NAME})\.)?(?P<name> {P_NAME}) # name group
(?P<params> ;?(?: {P_PARAM} )* ) # params group (may be empty)
: (?P<value> .* )$ # 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)


Expand Down Expand Up @@ -783,7 +765,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("_", "-"),
Expand All @@ -795,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
Expand Down Expand Up @@ -975,12 +953,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
Expand All @@ -996,13 +974,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)
Expand Down Expand Up @@ -1082,8 +1060,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:
Expand All @@ -1102,15 +1079,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:
Expand Down Expand Up @@ -1173,7 +1149,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:
Expand Down
12 changes: 4 additions & 8 deletions vobject/behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down
8 changes: 2 additions & 6 deletions vobject/hcalendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,7 @@ def out(s):
# TODO: Spec says we should handle when dtstart isn't included

out(
'<abbr class="dtstart", title="{0!s}">{1!s}</abbr>\r\n'.format(
dtstart.strftime(machine), dtstart.strftime(timeformat)
)
f'<abbr class="dtstart", title="{dtstart.strftime(machine)}">{dtstart.strftime(timeformat)}</abbr>\r\n'
)

# DTEND
Expand All @@ -108,9 +106,7 @@ def out(s):
human = dtend - timedelta(days=1)

out(
'- <abbr class="dtend", title="{0!s}">{1!s}</abbr>\r\n'.format(
dtend.strftime(machine), human.strftime(timeformat)
)
f'- <abbr class="dtend", title="{dtend.strftime(machine)}">{human.strftime(timeformat)}</abbr>\r\n'
)

# LOCATION
Expand Down
Loading

0 comments on commit 2094c71

Please sign in to comment.