diff --git a/inkex/__init__.py b/inkex/__init__.py index 9682fd9839543e6732a31c42deb1a20c9991209c..841dc393ffcfc486d7c11dbbe5d4e751ce98e6be 100644 --- a/inkex/__init__.py +++ b/inkex/__init__.py @@ -6,7 +6,6 @@ This provides the basis from which you can develop your inkscape extension. """ # pylint: disable=wildcard-import -from __future__ import print_function import sys MIN_VERSION = (3, 6) diff --git a/inkex/base.py b/inkex/base.py index d2c8585d78598197149ff154d2fd7a44b72235d5..a09543ac92189959c85f15055e30ed2f1dcf0c12 100644 --- a/inkex/base.py +++ b/inkex/base.py @@ -19,37 +19,25 @@ """ The ultimate base functionality for every Inkscape extension. """ -from __future__ import absolute_import, print_function, unicode_literals import os import sys import copy -import shutil + +from typing import Dict, List, Tuple, Type, Optional, Callable, Any, Union, IO, TYPE_CHECKING, cast from argparse import ArgumentParser, Namespace from lxml import etree -from .utils import PY3, filename_arg, AbortExtension, ABORT_STATUS, errormsg, do_nothing +from .utils import filename_arg, AbortExtension, ABORT_STATUS, errormsg, do_nothing from .elements._base import load_svg, BaseElement # pylint: disable=unused-import from .elements._utils import NSS from .localization import localize -stdout = sys.stdout - -try: - from typing import (Dict, List, Tuple, Type, Optional, Callable, Any, Union, IO, - TYPE_CHECKING, cast) -except ImportError: - cast = lambda x, y: y - TYPE_CHECKING = False +stdout = sys.stdout.buffer # type: ignore -if PY3: - unicode = str # pylint: disable=redefined-builtin,invalid-name - basestring = str # pylint: disable=redefined-builtin,invalid-name - stdout = sys.stdout.buffer # type: ignore - -class InkscapeExtension(object): +class InkscapeExtension: """ The base class extension, provides argument parsing and basic variable handling features. @@ -62,7 +50,7 @@ class InkscapeExtension(object): NSS.update(self.extra_nss) self.file_io = None # type: Optional[IO] self.options = Namespace() - self.document = None # type: Union[None, bytes, str, unicode, etree] + self.document = None # type: Union[None, bytes, str, etree] self.arg_parser = ArgumentParser(description=self.__doc__) self.arg_parser.add_argument( @@ -150,7 +138,7 @@ class InkscapeExtension(object): def load_raw(self): # type: () -> None """Load the input stream or filename, save everything to self""" - if isinstance(self.options.input_file, (str, unicode)): + if isinstance(self.options.input_file, str): self.file_io = open(self.options.input_file, 'rb') document = self.load(self.file_io) else: @@ -161,7 +149,7 @@ class InkscapeExtension(object): # type: (Any) -> None """Save to the output stream, use everything from self""" if self.has_changed(ret): - if isinstance(self.options.output, (str, unicode)): + if isinstance(self.options.output, str): with open(self.options.output, 'wb') as stream: self.save(stream) else: @@ -286,22 +274,23 @@ class TempDirMixin(_Base): def __init__(self, *args, **kwargs): self.tempdir = None - super(TempDirMixin, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def load_raw(self): # type: () -> None """Create the temporary directory""" - from tempfile import mkdtemp - self.tempdir = os.path.realpath( - mkdtemp(self.dir_suffix, self.dir_prefix, None)) - super(TempDirMixin, self).load_raw() + from tempfile import TemporaryDirectory + # Need to hold a reference to the Directory object or else it might get GC'd + self._tempdir = TemporaryDirectory(prefix=self.dir_prefix, suffix=self.dir_suffix) + self.tempdir = self._tempdir.name + super().load_raw() def clean_up(self): # type: () -> None """Delete the temporary directory""" - if self.tempdir and os.path.isdir(self.tempdir): - shutil.rmtree(self.tempdir) - super(TempDirMixin, self).clean_up() + self.tempdir = None + self._tempdir.cleanup() + super().clean_up() class SvgInputMixin(_Base): # pylint: disable=too-few-public-methods @@ -312,7 +301,7 @@ class SvgInputMixin(_Base): # pylint: disable=too-few-public-methods select_all = () # type: Tuple[Type[BaseElement], ...] def __init__(self): - super(SvgInputMixin, self).__init__() + super().__init__() self.arg_parser.add_argument( "--id", action="append", type=str, dest="ids", default=[], @@ -360,15 +349,14 @@ class SvgOutputMixin(_Base): # pylint: disable=too-few-public-methods def save(self, stream): # type: (IO) -> None """Save the svg document to the given stream""" - if isinstance(self.document, (bytes, str, unicode)): + if isinstance(self.document, (bytes, str)): document = self.document elif 'Element' in type(self.document).__name__: # isinstance can't be used here because etree is broken doc = cast(etree, self.document) document = doc.getroot().tostring() else: - raise ValueError("Unknown type of document: {} can not save."\ - .format(type(self.document).__name__)) + raise ValueError(f"Unknown type of document: {type(self.document).__name__} can not save.") try: stream.write(document) diff --git a/inkex/colors.py b/inkex/colors.py index bb11b709462c517b247604010c37396a6eceec17..7db26dc950b4a01865173d09b5ac73f99ac32ca1 100644 --- a/inkex/colors.py +++ b/inkex/colors.py @@ -22,15 +22,11 @@ Basic color controls """ -from .utils import PY3 from .tween import interpcoord # All the names that get added to the inkex API itself. __all__ = ('Color', 'ColorError', 'ColorIdError') -if PY3: - unicode = str # pylint: disable=redefined-builtin,invalid-name - SVG_COLOR = { 'aliceblue': '#f0f8ff', 'antiquewhite': '#faebd7', @@ -221,11 +217,11 @@ class Color(list): lightness = lightness.setter(lambda self, value: self._set(2, value, ('hsl',))) def __init__(self, color=None, space='rgb'): - super(Color, self).__init__() + super().__init__() if isinstance(color, Color): space, color = color.space, list(color) - if isinstance(color, (str, unicode)): + if isinstance(color, str): # String from xml or css attributes space, color = self.parse_str(color.strip()) @@ -274,7 +270,7 @@ class Color(list): if len(self) == len(self.space): raise ValueError("Can't add any more values to color.") - if isinstance(val, (unicode, str)): + if isinstance(val, str): val = val.strip() if val.endswith('%'): val = float(val.strip('%')) / 100 @@ -289,7 +285,7 @@ class Color(list): val *= 255 if isinstance(val, (int, float)): - super(Color, self).append(max(end_type(val), 0)) + super().append(max(end_type(val), 0)) @staticmethod def parse_str(color): diff --git a/inkex/command.py b/inkex/command.py index 7d30835ce87d247e360165a27310ff4f33aca1a6..c7d2ecf3a4abae6696b569f4ce3cd8fc0e306fe6 100644 --- a/inkex/command.py +++ b/inkex/command.py @@ -33,9 +33,9 @@ it yourself, to take advantage of the security settings and testing functions. import os from subprocess import Popen, PIPE +from tempfile import TemporaryDirectory from lxml.etree import ElementTree -from .utils import TemporaryDirectory, PY3 from .elements import SvgDocumentElement INKSCAPE_EXECUTABLE_NAME = os.environ.get('INKSCAPE_COMMAND', 'inkscape') @@ -54,24 +54,11 @@ def which(program): """ if os.path.isabs(program) and os.path.isfile(program): return program - try: - # Python2 and python3, but must have distutils and may not always - # work on windows versions (depending on the version) - from distutils.spawn import find_executable - prog = find_executable(program) - if prog: - return prog - except ImportError: - pass - try: - # Python3 only version of which - from shutil import which as warlock - prog = warlock(program) - if prog: + from shutil import which as warlock + prog = warlock(program) + if prog: return prog - except ImportError: - pass # python2 # There may be other methods for doing a `which` command for other # operating systems; These should go here as they are discovered. @@ -159,7 +146,7 @@ def to_args(prog, *positionals, **arguments): def _call(program, *args, **kwargs): stdin = kwargs.pop('stdin', None) - if PY3 and isinstance(stdin, str): + if isinstance(stdin, str): stdin = stdin.encode('utf-8') inpipe = PIPE if stdin else None @@ -198,8 +185,8 @@ def inkscape_command(svg, select=None, verbs=()): inkscape_command('', ('verb', 'VerbName'), ...) """ - with TemporaryDirectory(prefix='inkscape-command') as dirname: - svg_file = write_svg(svg, dirname, 'input.svg') + with TemporaryDirectory(prefix='inkscape-command') as tmpdir: + svg_file = write_svg(svg, tmpdir, 'input.svg') select = ('select', select) if select else None verbs += ('FileSave', 'FileQuit') inkscape(svg_file, select, batch_process=True, verb=';'.join(verbs)) diff --git a/inkex/elements/_base.py b/inkex/elements/_base.py index 23968ab4c51e3e6a21653af7b98bb0b7ce67c837..350aa5c55ff3cfcd148f9c8eed7e1c48734f42e7 100644 --- a/inkex/elements/_base.py +++ b/inkex/elements/_base.py @@ -33,13 +33,10 @@ from lxml import etree from ..paths import Path from ..styles import Style, AttrFallbackStyle, Classes from ..transforms import Transform, BoundingBox -from ..utils import PY3, FragmentError +from ..utils import FragmentError from ._utils import ChildToProperty, NSS, addNS, removeNS, splitNS -try: - from typing import overload, DefaultDict, Type, Any, List, Tuple, Union, Optional # pylint: disable=unused-import -except ImportError: - overload = lambda x: x +from typing import overload, DefaultDict, Type, Any, List, Tuple, Union, Optional # pylint: disable=unused-import class NodeBasedLookup(etree.PythonElementClassLookup): """ @@ -110,7 +107,7 @@ class BaseElement(etree.ElementBase): def TAG(self): # pylint: disable=invalid-name """Return the tag_name without NS""" if not self.tag_name: - return removeNS(super(etree.ElementBase, self).tag)[-1] + return removeNS(super().tag)[-1] return removeNS(self.tag_name)[-1] @classmethod @@ -178,7 +175,7 @@ class BaseElement(etree.ElementBase): else: self.attrib.pop(attr, None) # pylint: disable=no-member else: - super(BaseElement, self).__setattr__(name, value) + super().__setattr__(name, value) def get(self, attr, default=None): """Get element attribute named, with addNS support.""" @@ -189,7 +186,7 @@ class BaseElement(etree.ElementBase): # transformations and style attributes are equiv to not-existing ret = str(value) if value else (default or None) return ret - return super(BaseElement, self).get(addNS(attr), default) + return super().get(addNS(attr), default) def set(self, attr, value): """Set element attribute named, with addNS support""" @@ -203,8 +200,8 @@ class BaseElement(etree.ElementBase): if value is None: self.attrib.pop(addNS(attr), None) # pylint: disable=no-member else: - value = str(value) if PY3 else unicode(value) # pylint: disable=undefined-variable - super(BaseElement, self).set(addNS(attr), value) + value = str(value) + super().set(addNS(attr), value) def update(self, **kwargs): """ @@ -345,11 +342,11 @@ class BaseElement(etree.ElementBase): def xpath(self, pattern, namespaces=NSS): # pylint: disable=dangerous-default-value """Wrap xpath call and add svg namespaces""" - return super(BaseElement, self).xpath(pattern, namespaces=namespaces) + return super().xpath(pattern, namespaces=namespaces) def findall(self, pattern, namespaces=NSS): # pylint: disable=dangerous-default-value """Wrap findall call and add svg namespaces""" - return super(BaseElement, self).findall(pattern, namespaces=namespaces) + return super().findall(pattern, namespaces=namespaces) def findone(self, xpath): """Gets a single element from the given xpath or returns None""" diff --git a/inkex/elements/_filters.py b/inkex/elements/_filters.py index 4ecf64a49dc3239115924e631ab64d7113673a83..40ed329ef8506ef72725233bad8f9fd3762851a4 100644 --- a/inkex/elements/_filters.py +++ b/inkex/elements/_filters.py @@ -35,10 +35,7 @@ from ._utils import addNS from ._base import BaseElement -try: - from typing import overload, Iterable, List, Tuple, Union, Optional # pylint: disable=unused-import -except ImportError: - overload = lambda x: x +from typing import overload, Iterable, List, Tuple, Union, Optional # pylint: disable=unused-import class Filter(BaseElement): diff --git a/inkex/elements/_groups.py b/inkex/elements/_groups.py index 260f91f647827b64fbbca8597f1cac9fc99ed160..390580f1b4555323bd33f843a428eacedc061719 100644 --- a/inkex/elements/_groups.py +++ b/inkex/elements/_groups.py @@ -63,7 +63,7 @@ class Group(GroupBase): @classmethod def new(cls, label, *children, **attrs): attrs['inkscape:label'] = label - return super(Group, cls).new(*children, **attrs) + return super().new(*children, **attrs) def effective_style(self): @@ -98,7 +98,7 @@ class Anchor(GroupBase): @classmethod def new(cls, href, *children, **attrs): attrs['xlink:href'] = href - return super(Anchor, cls).new(*children, **attrs) + return super().new(*children, **attrs) class ClipPath(GroupBase): diff --git a/inkex/elements/_meta.py b/inkex/elements/_meta.py index 2c775b770b151dbd157bf5b15bfa60f7b7b7b0b8..b90c504f0923802945b3cfe56ba23d74604082e1 100644 --- a/inkex/elements/_meta.py +++ b/inkex/elements/_meta.py @@ -104,7 +104,7 @@ class Guide(BaseElement): @classmethod def new(cls, pos_x, pos_y, angle, **attrs): - guide = super(Guide, cls).new(**attrs) + guide = super().new(**attrs) guide.move_to(pos_x, pos_y, angle=angle) return guide diff --git a/inkex/elements/_polygons.py b/inkex/elements/_polygons.py index 7431b39acb1ec96faaa9c42be1cea34e922ddb54..a2368ff797b8c4fce72a0b806a0330132b779698 100644 --- a/inkex/elements/_polygons.py +++ b/inkex/elements/_polygons.py @@ -36,7 +36,7 @@ class PathElementBase(ShapeElement): @classmethod def new(cls, path, **attrs): - return super(PathElementBase, cls).new(d=Path(path), **attrs) + return super().new(d=Path(path), **attrs) def set_path(self, path): """Set the given data as a path as the 'd' attribute""" @@ -123,7 +123,7 @@ class Line(ShapeElement): def new(cls, start, end, **attrs): start = Vector2d(start) end = Vector2d(end) - return super(Line, cls).new(x1=start.x, y1=start.y, + return super().new(x1=start.x, y1=start.y, x2=end.x, y2=end.y, **attrs) @@ -190,7 +190,7 @@ class EllipseBase(ShapeElement): @classmethod def new(cls, center, radius, **attrs): - circle = super(EllipseBase, cls).new(**attrs) + circle = super().new(**attrs) circle.center = center circle.radius = radius return circle diff --git a/inkex/extensions.py b/inkex/extensions.py index 87d5348c30ae5f7aeb8bb6855c244c179708f341..c652b4852a044a3d11f54d7b84e2c38098236b8e 100644 --- a/inkex/extensions.py +++ b/inkex/extensions.py @@ -28,7 +28,7 @@ import re import sys import types -from .utils import errormsg, Boolean, PY3 +from .utils import errormsg, Boolean from .colors import Color, ColorIdError, ColorError from .elements import load_svg, BaseElement, ShapeElement, Group, Layer, Grid, \ TextElement, FlowPara, FlowDiv @@ -42,8 +42,7 @@ __all__ = ('EffectExtension', 'GenerateExtension', 'InputExtension', 'CallExtension', 'TemplateExtension', 'ColorExtension', 'TextExtension') stdout = sys.stdout -if PY3: - unicode = str # pylint: disable=redefined-builtin,invalid-name + class EffectExtension(SvgThroughMixin, InkscapeExtension): """ @@ -110,7 +109,7 @@ class CallExtension(TempDirMixin, InputExtension): TempDirMixin.load_raw(self) input_file = self.options.input_file - if not isinstance(input_file, (unicode, str)): + if not isinstance(input_file, str): data = input_file.read() input_file = os.path.join(self.tempdir, 'input.' + self.input_ext) with open(input_file, 'wb') as fhl: @@ -118,7 +117,7 @@ class CallExtension(TempDirMixin, InputExtension): output_file = os.path.join(self.tempdir, 'output.' + self.output_ext) document = self.call(input_file, output_file) or output_file - if isinstance(document, (str, unicode)): + if isinstance(document, str): if not os.path.isfile(document): raise IOError(f"Can't find generated document: {document}") @@ -126,7 +125,7 @@ class CallExtension(TempDirMixin, InputExtension): with open(document, 'r') as fhl: document = fhl.read() if '<' in document: - document = load_svg(document) + document = load_svg(document.encode('utf-8')) else: with open(document, 'rb') as fhl: document = fhl.read() @@ -208,7 +207,7 @@ class TemplateExtension(EffectExtension): template_id = "SVGRoot" def __init__(self): - super(TemplateExtension, self).__init__() + super().__init__() # Arguments added on after add_arguments so it can be overloaded cleanly. self.arg_parser.add_argument("--size", type=self.arg_size(), dest="size") self.arg_parser.add_argument("--width", type=int, default=800) diff --git a/inkex/inx.py b/inkex/inx.py index 539507d016cadffd4d6afe7d1ca06782591bd5a4..d9c8cb19dc6ad3c9805b1211ef8d975b04928f42 100644 --- a/inkex/inx.py +++ b/inkex/inx.py @@ -21,14 +21,10 @@ Parsing inx files for checking and generating. """ import os +from inspect import isclass +from importlib import util from lxml import etree -try: - from inspect import isclass - from importlib import util -except ImportError: - util = None # type: ignore - from .base import InkscapeExtension from .utils import Boolean @@ -48,7 +44,7 @@ class InxLookup(etree.CustomElementClassLookup): INX_PARSER = etree.XMLParser() INX_PARSER.set_element_class_lookup(InxLookup()) -class InxFile(object): +class InxFile: """Open an INX file and provide useful functions""" name = property(lambda self: self.xml._text('name')) ident = property(lambda self: self.xml._text('id')) @@ -87,7 +83,7 @@ class InxFile(object): def extension_class(self): """Attempt to get the extension class""" script = self.script.get('script', None) - if script is not None and util is not None: + if script is not None: name = script[:-3].replace('/', '.') spec = util.spec_from_file_location(name, script) mod = util.module_from_spec(spec) diff --git a/inkex/paths.py b/inkex/paths.py index 8a86a6b7e5d0b9fe83eca038bacfec366a7ea415..7ac5feb90ff023b8d4c265835883802edf1ace95 100644 --- a/inkex/paths.py +++ b/inkex/paths.py @@ -28,13 +28,10 @@ from math import atan2, cos, pi, sin, sqrt, acos, tan from .transforms import Transform, BoundingBox, Vector2d from .utils import classproperty, strargs -try: # pylint: disable=using-constant-test - from typing import overload, Any, Type, Dict, Optional, Union, Tuple, List, Iterator, Generator # pylint: disable=unused-import - from typing import TypeVar - Pathlike = TypeVar('Pathlike', bound="PathCommand") - AbsolutePathlike = TypeVar('AbsolutePathlike', bound="AbsolutePathCommand") -except ImportError: - overload = lambda x: x +from typing import overload, Any, Type, Dict, Optional, Union, Tuple, List, Iterator, Generator # pylint: disable=unused-import +from typing import TypeVar +Pathlike = TypeVar('Pathlike', bound="PathCommand") +AbsolutePathlike = TypeVar('AbsolutePathlike', bound="AbsolutePathCommand") # All the names that get added to the inkex API itself. __all__ = ( @@ -62,7 +59,7 @@ class InvalidPath(ValueError): """Raised when given an invalid path string""" -class PathCommand(object): +class PathCommand: """ Base class of all path commands """ @@ -1105,7 +1102,7 @@ PathCommand._letter_to_class = { class Path(list): """A list of segment commands which combine to draw a shape""" - class PathCommandProxy(object): + class PathCommandProxy: """ A handy class for Path traverse and coordinate access @@ -1166,7 +1163,7 @@ class Path(list): return "<" + self.__class__.__name__ + ">" + repr(self.command) def __init__(self, path_d=None): - super(Path, self).__init__() + super().__init__() if isinstance(path_d, str): # Returns a generator returning PathCommand objects path_d = self.parse_string(path_d) @@ -1443,7 +1440,7 @@ class CubicSuperPath(list): """ def __init__(self, items): - super(CubicSuperPath, self).__init__() + super().__init__() self._closed = True self._prev = Vector2d() self._prev_prev = Vector2d() @@ -1469,7 +1466,7 @@ class CubicSuperPath(list): if isinstance(item, PathCommand): if isinstance(item, Move): if self._closed is False: - super(CubicSuperPath, self).append([]) + super().append([]) item = [list(item.args), list(item.args), list(item.args)] elif isinstance(item, ZoneClose) and self and self[-1]: # This duplicates the first segment to 'close' the path, it's appended directly @@ -1505,7 +1502,7 @@ class CubicSuperPath(list): if len(item) != 3 or not all([len(bit) == 2 for bit in item]): # The item is already a subpath (usually from some other process) if len(item[0]) == 3 and all([len(bit) == 2 for bit in item[0]]): - super(CubicSuperPath, self).append(self._clean(item)) + super().append(self._clean(item)) self._prev_prev = Vector2d(self[-1][-1][0]) self._prev = Vector2d(self[-1][-1][1]) return @@ -1515,7 +1512,7 @@ class CubicSuperPath(list): # Closed means that the previous segment is closed so we need a new one # We always append to the last open segment. CSP starts out closed. self._closed = False - super(CubicSuperPath, self).append([]) + super().append([]) if self[-1]: # The last tuple is replaced, it's the coords of where the next segment will land. diff --git a/inkex/ports.py b/inkex/ports.py index f7c5f5d79d7406ac3a189b2853ff77e2f2e0ac35..f8e31091409cc43fa01b759d28507aa79a56d184 100644 --- a/inkex/ports.py +++ b/inkex/ports.py @@ -31,7 +31,7 @@ try: except ImportError: serial = None -class Serial(object): +class Serial: """ Attempt to get access to the computer's serial port. diff --git a/inkex/styles.py b/inkex/styles.py index bd50de6e639c39c819ed6f245346391d90e87363..e98682594bcdde62f205276c45fbcab9c40558c4 100644 --- a/inkex/styles.py +++ b/inkex/styles.py @@ -24,20 +24,17 @@ and some color handling on top. import re from collections import OrderedDict -from .utils import PY3 from .colors import Color, ColorIdError from .tween import interpcoord, interpunit -if PY3: - unicode = str # pylint: disable=redefined-builtin,invalid-name class Classes(list): """A list of classes applied to an element (used in css and js)""" def __init__(self, classes=None, callback=None): self.callback = None - if isinstance(classes, (str, unicode)): + if isinstance(classes, str): classes = classes.split() - super(Classes, self).__init__(classes or ()) + super().__init__(classes or ()) self.callback = callback def __str__(self): @@ -48,19 +45,19 @@ class Classes(list): self.callback(self) def __setitem__(self, index, value): - super(Classes, self).__setitem__(index, value) + super().__setitem__(index, value) self._callback() def append(self, value): value = str(value) if value not in self: - super(Classes, self).append(value) + super().append(value) self._callback() def remove(self, value): value = str(value) if value in self: - super(Classes, self).remove(value) + super().remove(value) self._callback() def toggle(self, value): @@ -81,13 +78,13 @@ class Style(OrderedDict): self.callback = None # Either a string style or kwargs (with dashes as underscores). style = style or [(k.replace('_', '-'), v) for k, v in kw.items()] - if isinstance(style, (str, unicode)): + if isinstance(style, str): style = self.parse_str(style) # Order raw dictionaries so tests can be made reliable if isinstance(style, dict) and not isinstance(style, OrderedDict): style = [(name, style[name]) for name in sorted(style)] # Should accept dict, Style, parsed string, list etc. - super(Style, self).__init__(style) + super().__init__(style) # Now after the initial data, the callback makes sense. self.callback = callback @@ -145,12 +142,12 @@ class Style(OrderedDict): def update(self, other): """Make sure callback is called when updating""" - super(Style, self).update(Style(other)) + super().update(Style(other)) if self.callback is not None: self.callback(self) def __setitem__(self, key, value): - super(Style, self).__setitem__(key, value) + super().__setitem__(key, value) if self.callback is not None: self.callback(self) @@ -169,8 +166,8 @@ class Style(OrderedDict): def update_urls(self, old_id, new_id): """Find urls in this style and replace them with the new id""" for (name, value) in self.items(): - if value == 'url(#{})'.format(old_id): - self[name] = 'url(#{})'.format(new_id) + if value == f"url(#{old_id})": + self[name] = f"url(#{new_id})" def interpolate_prop(self, other, fraction, prop, svg=None): """Interpolate specific property.""" @@ -205,7 +202,7 @@ class Style(OrderedDict): return style -class AttrFallbackStyle(object): +class AttrFallbackStyle: """ A container for a style and an element that may have competing styles @@ -263,7 +260,7 @@ class StyleSheets(list): re-created on the fly by lxml so lookups have to be centralised. """ def __init__(self, svg=None): - super(StyleSheets, self).__init__() + super().__init__() self.svg = svg def lookup(self, element_id, svg=None): @@ -286,7 +283,7 @@ class StyleSheet(list): comment_strip = re.compile(r"//.*?\n") def __init__(self, content=None, callback=None): - super(StyleSheet, self).__init__() + super().__init__() self.callback = None # Remove comments content = self.comment_strip.sub('', (content or '')) @@ -314,7 +311,7 @@ class StyleSheet(list): return # Warning? rules, style = other.strip('}').split('{', 1) other = ConditionalStyle(rules=rules, style=style.strip(), callback=self._callback) - super(StyleSheet, self).append(other) + super().append(other) self._callback() def lookup(self, element_id, svg): @@ -331,7 +328,7 @@ class ConditionalStyle(Style): rather than being an attribute style. """ def __init__(self, rules='*', style=None, callback=None, **kwargs): - super(ConditionalStyle, self).__init__(style=style, callback=callback, **kwargs) + super().__init__(style=style, callback=callback, **kwargs) self.rules = [ConditionalRule(rule) for rule in rules.split(',')] def __str__(self): @@ -349,7 +346,7 @@ class ConditionalStyle(Style): # this xpath transform and provides no extra functionality for reverse lookups. return '|'.join([rule.to_xpath() for rule in self.rules]) -class ConditionalRule(object): +class ConditionalRule: """A single css rule""" step_to_xpath = [ (re.compile(r'\[(\w+)\^=([^\]]+)\]'), r'[starts-with(@\1,\2)]'), # Starts With diff --git a/inkex/tester/__init__.py b/inkex/tester/__init__.py index ddab3070a44626348261638d52253d7e502cdcb0..e1b0e5e30de72593e476ba280c6f94de10478699 100644 --- a/inkex/tester/__init__.py +++ b/inkex/tester/__init__.py @@ -66,8 +66,6 @@ of the `.export` suffix. pytest should then be re-run to confirm before committing to the repository. """ -from __future__ import absolute_import, print_function, unicode_literals - import os import re import sys @@ -84,7 +82,7 @@ from unittest import TestCase as BaseCase from inkex.base import InkscapeExtension from .. import Transform -from ..utils import PY3, to_bytes +from ..utils import to_bytes from .xmldiff import xmldiff from .mock import MockCommandMixin, Capture @@ -116,29 +114,20 @@ class TestCase(MockCommandMixin, BaseCase): stderr_output = False stdout_protect = True stderr_protect = True - python3_only = False def __init__(self, *args, **kw): - super(TestCase, self).__init__(*args, **kw) + super().__init__(*args, **kw) self._temp_dir = None self._effect = None def setUp(self): # pylint: disable=invalid-name """Make sure every test is seeded the same way""" self._effect = None - super(TestCase, self).setUp() - if self.python3_only and not PY3: - self.skipTest("No available in python2") - try: - # python3, with version 1 to get the same numbers - # as in python2 during tests. - random.seed(0x35f, version=1) - except TypeError: - # But of course this kwarg doesn't exist in python2 - random.seed(0x35f) + super().setUp() + random.seed(0x35f) def tearDown(self): - super(TestCase, self).tearDown() + super().tearDown() if self._temp_dir and os.path.isdir(self._temp_dir): shutil.rmtree(self._temp_dir) @@ -265,7 +254,7 @@ class TestCase(MockCommandMixin, BaseCase): self._effect = self.effect_class() return self._effect -class InkscapeExtensionTestMixin(object): +class InkscapeExtensionTestMixin: """Automatically setup self.effect for each test and test with an empty svg""" def setUp(self): # pylint: disable=invalid-name """Check if there is an effect_class set and create self.effect if it is""" @@ -277,7 +266,7 @@ class InkscapeExtensionTestMixin(object): """Extension works with empty svg file""" self.effect.run([self.empty_svg]) -class ComparisonMixin(object): +class ComparisonMixin: """ Add comparison tests to any existing test suite. """ diff --git a/inkex/tester/filters.py b/inkex/tester/filters.py index 6fd4f0ab0477438b16c1ecff138cef155080668e..c6703716dfeee76a69d4e9c40fa5efa706dedebb 100644 --- a/inkex/tester/filters.py +++ b/inkex/tester/filters.py @@ -33,7 +33,7 @@ filters that are being used. import re from ..utils import to_bytes -class Compare(object): +class Compare: """ Comparison base class, this acts as a passthrough unless the filter staticmethod is overwritten. diff --git a/inkex/tester/mock.py b/inkex/tester/mock.py index bf9ba19a1d6d6db80656a61beb43f2a5cb0f3d34..c6507fe4e60bf6fb1e599b9274cb2e504ecac366 100644 --- a/inkex/tester/mock.py +++ b/inkex/tester/mock.py @@ -43,7 +43,7 @@ if False: # pylint: disable=using-constant-test FIXED_BOUNDARY = '--CALLDATA--//--CALLDATA--' -class Capture(object): +class Capture: """Capture stdout or stderr. Used as `with Capture('stdout') as stream:`""" def __init__(self, io_name='stdout', swap=True): self.io_name = io_name @@ -52,8 +52,6 @@ class Capture(object): self.swap = swap def __enter__(self): - # We can't control python2 correctly (unicode vs. bytes-like) but - # we don't need it, so we're ignore python2 as if it doesn't exist. if self.swap: setattr(sys, self.io_name, self.stream) return self.stream @@ -64,7 +62,7 @@ class Capture(object): self.original.write(self.stream.getvalue()) setattr(sys, self.io_name, self.original) -class ManualVerbosity(object): +class ManualVerbosity: """Change the verbosity of the test suite manually""" result = property(lambda self: self.test._current_result) @@ -82,7 +80,7 @@ class ManualVerbosity(object): __exit__ = flip -class MockMixin(object): +class MockMixin: """ Add mocking ability to any test base class, will set up mock on setUp and remove it on tearDown. @@ -118,7 +116,7 @@ class MockMixin(object): def setUp(self): # pylint: disable=invalid-name """For each mock instruction, set it up and store the return""" - super(MockMixin, self).setUp() + super().setUp() for x, mock in enumerate(self.mocks): if len(mock) == 4: logging.error("Mock was already set up, so it wasn't cleared previously!") @@ -127,7 +125,7 @@ class MockMixin(object): def tearDown(self): # pylint: disable=invalid-name """For each returned stored, tear it down and restore mock instruction""" - super(MockMixin, self).tearDown() + super().tearDown() try: for x, (owner, name, old, _) in enumerate(self.mocks): self.mocks[x] = (owner, name, getattr(owner, name)) @@ -155,7 +153,7 @@ class MockCommandMixin(MockMixin): recorded_tempdirs = [] # type:List[str] def setUp(self): # pylint: disable=invalid-name - super(MockCommandMixin, self).setUp() + super().setUp() # This is a the daftest thing I've ever seen, when in the middle # of a mock, the 'self' variable magically turns from a FooTest # into a TestCase, this makes it impossible to find the datadir. diff --git a/inkex/transforms.py b/inkex/transforms.py index 460e9b7a240b9aa35619a9770c2b5748c5eb6e46..a253ed83cdf69163da5153ba9ecec3ce8d201c22 100644 --- a/inkex/transforms.py +++ b/inkex/transforms.py @@ -31,17 +31,13 @@ from decimal import Decimal from math import cos, radians, sin, sqrt, tan, fabs, atan2, hypot, pi, isfinite from .tween import interpcoord -from .utils import strargs, KeyDict, PY3 +from .utils import strargs, KeyDict -try: - from typing import overload, cast, List, Any, Callable, Generator, Iterator, Tuple, Union, Optional, Sequence # pylint: disable=unused-import +from typing import overload, cast, List, Any, Callable, Generator, Iterator, Tuple, Union, Optional, Sequence # pylint: disable=unused-import - VectorLike = Union["ImmutableVector2d", Tuple[float, float]] # pylint: disable=invalid-name - MatrixLike = Union[str, Tuple[Tuple[float,float,float], Tuple[float,float,float]], Tuple[float,float,float,float,float,float], "Transform"] - BoundingIntervalArgs = Union['BoundingInterval', Tuple[float, float], float] # pylint: disable=invalid-name -except ImportError: - overload = lambda x: x - cast = lambda x, y: y +VectorLike = Union["ImmutableVector2d", Tuple[float, float]] # pylint: disable=invalid-name +MatrixLike = Union[str, Tuple[Tuple[float,float,float], Tuple[float,float,float]], Tuple[float,float,float,float,float,float], "Transform"] +BoundingIntervalArgs = Union['BoundingInterval', Tuple[float, float], float] # pylint: disable=invalid-name # All the names that get added to the inkex API itself. __all__ = ( @@ -52,8 +48,6 @@ __all__ = ( 'Vector2d', ) -if PY3: - unicode = str # pylint: disable=redefined-builtin,invalid-name # Old settings, supported because users click 'ok' without looking. XAN = KeyDict({'l': 'left', 'r': 'right', 'm': 'center_x'}) @@ -63,7 +57,7 @@ CUSTOM_DIRECTION = {270: 'tb', 90: 'bt', 0: 'lr', 360: 'lr', 180: 'rl'} DIRECTION = ['tb', 'bt', 'lr', 'rl', 'ro', 'ri'] -class ImmutableVector2d(object): +class ImmutableVector2d: """Represents an immutable element of 2-dimensional Euclidean space""" _x = 0.0 _y = 0.0 @@ -303,7 +297,7 @@ class Vector2d(ImmutableVector2d): -class Transform(object): +class Transform: """A transformation object which will always reduce to a matrix and can then be used in combination with other transformations for reducing finding a point and printing svg ready output. @@ -345,7 +339,7 @@ class Transform(object): def _set_matrix(self, matrix): # type: (MatrixLike) -> None """Parse a given string as an svg transformation instruction.""" - if isinstance(matrix, (str, unicode)): + if isinstance(matrix, str): for func, values in self.TRM.findall(matrix.strip()): getattr(self, 'add_' + func.lower())(*strargs(values)) elif isinstance(matrix, Transform): @@ -621,7 +615,7 @@ class Transform(object): interpcoord(self.f, other.f, fraction))) -class BoundingInterval(object): # pylint: disable=too-few-public-methods +class BoundingInterval: # pylint: disable=too-few-public-methods """A pair of numbers that represent the minimum and maximum values.""" @overload @@ -764,7 +758,7 @@ class BoundingInterval(object): # pylint: disable=too-few-public-methods return self.maximum - self.minimum -class BoundingBox(object): # pylint: disable=too-few-public-methods +class BoundingBox: # pylint: disable=too-few-public-methods """ Some functions to compute a rough bbox of a given list of objects. @@ -929,7 +923,7 @@ class BoundingBox(object): # pylint: disable=too-few-public-methods return [y, -y, x, -x, rot, -rot][DIRECTION.index(direction)] -class DirectedLineSegment(object): +class DirectedLineSegment: """ A directed line segment diff --git a/inkex/turtle.py b/inkex/turtle.py index cee7dadb56e105b75d266718e9e748c04d93195e..f2869b942875b700d41add289f2d396a6300d72e 100644 --- a/inkex/turtle.py +++ b/inkex/turtle.py @@ -21,7 +21,7 @@ import math import random -class pTurtle(object): +class pTurtle: """A Python path turtle""" def __init__(self, home=(0, 0)): diff --git a/inkex/tween.py b/inkex/tween.py index 31c165b17c384878267b549bd8c5e24eb40b3dcb..b3e76fdb458803ea36fd214f01a9d0d981461432 100644 --- a/inkex/tween.py +++ b/inkex/tween.py @@ -23,13 +23,10 @@ from bisect import bisect_left from .utils import X, Y from .units import convert_unit, parse_unit, render_unit -try: - from typing import Union, Tuple, List, TypeVar, Callable, overload - hasTypes = True - Value = TypeVar('Value') - Number = TypeVar('Number', int, float) -except ImportError: - pass +from typing import Union, Tuple, List, TypeVar, Callable, overload +hasTypes = True +Value = TypeVar('Value') +Number = TypeVar('Number', int, float) def interpcoord( diff --git a/inkex/utils.py b/inkex/utils.py index aed4f28710a2e14e6d7851ebd095764ca0e00a0c..ca1a4a3e2f96ec0746646fffeb2f4294af4ca64e 100644 --- a/inkex/utils.py +++ b/inkex/utils.py @@ -20,20 +20,14 @@ """ Basic common utility functions for calculated things """ -from __future__ import absolute_import, print_function, unicode_literals - import os import sys -import shutil import random import math from itertools import tee from argparse import ArgumentTypeError -# When python2 support is gone, enable tempfile's version -# from tempfile import TemporaryDirectory - # All the names that get added to the inkex API itself. __all__ = ('AbortExtension', 'DependencyError', 'Boolean', 'errormsg') @@ -42,9 +36,6 @@ ABORT_STATUS = -5 (X, Y) = range(2) PY3 = sys.version_info[0] == 3 -if PY3: - unicode = str # pylint: disable=redefined-builtin,invalid-name - def _pythonpath(): for pth in os.environ.get('PYTHONPATH', '').split(':'): if os.path.isdir(pth): @@ -68,6 +59,7 @@ def get_inkscape_directory(): if os.path.isdir(os.path.join(pth, 'inkex')): return pth + class KeyDict(dict): """ A normal dictionary, except asking for anything not in the dictionary @@ -75,23 +67,10 @@ class KeyDict(dict): """ def __getitem__(self, key): try: - return super(KeyDict, self).__getitem__(key) + return super().__getitem__(key) except KeyError: return key -class TemporaryDirectory(object): # pylint: disable=too-few-public-methods - """Tiny replacement for python3's version.""" - def __init__(self, suffix="", prefix="tmp"): - self.suffix = suffix - self.prefix = prefix - self.path = None - def __enter__(self): - from tempfile import mkdtemp - self.path = mkdtemp(self.suffix, self.prefix, None) - return self.path - def __exit__(self, exc, value, traceback): - if os.path.isdir(self.path): - shutil.rmtree(self.path) def Boolean(value): """ArgParser function to turn a boolean string into a python boolean""" @@ -132,7 +111,7 @@ def errormsg(msg): try: sys.stderr.write(msg) except TypeError: - sys.stderr.write(unicode(msg)) + sys.stderr.write(str(msg)) except UnicodeEncodeError: # Python 2: # Fallback for cases where sys.stderr.encoding is not Unicode. @@ -188,7 +167,7 @@ def strargs(string, kind=float): return [kind(val) for val in string.replace(',', ' ').replace('-', ' -').replace('e ', 'e').replace('E ','e').split()] -class classproperty(object): # pylint: disable=invalid-name, too-few-public-methods +class classproperty: # pylint: disable=invalid-name, too-few-public-methods """Combine classmethod and property decorators""" def __init__(self, func): diff --git a/tests/test_inkex_bounding_box.py b/tests/test_inkex_bounding_box.py index e074476c8c0e497433fca1f651c0637052b83ae7..a44dacc88e3a7377045d2c5c35e93f76c91cc288 100644 --- a/tests/test_inkex_bounding_box.py +++ b/tests/test_inkex_bounding_box.py @@ -17,7 +17,7 @@ from inkex import ( Style ) from inkex.tester import TestCase -from inkex.utils import TemporaryDirectory +from tempfile import TemporaryDirectory from inkex.command import is_inkscape_available from inkex.tester.decorators import requires_inkscape try: