Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Python 2 compatibility bits #552

Merged
merged 8 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 6 additions & 63 deletions pygal/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,76 +18,19 @@
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""Various hacks for former transparent python 2 / python 3 support"""

import sys

try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable

from datetime import datetime, timedelta, tzinfo

base = (str, bytes)
coerce = str
_ellipsis = eval('...')
import datetime
from collections.abc import Iterable


def is_list_like(value):
"""Return whether value is an iterable but not a mapping / string"""
return isinstance(value, Iterable) and not isinstance(value, (base, dict))


def is_str(string):
"""Return whether value is a string or a byte list"""
return isinstance(string, base)


def to_str(obj):
"""Cast obj to unicode string"""
if not is_str(obj):
return coerce(obj)
return obj


def to_unicode(string):
"""Force string to be a string in python 3 or a unicode in python 2"""
if not isinstance(string, coerce):
return string.decode('utf-8')
return string


def u(s):
"""Emulate u'str' in python 2, do nothing in python 3"""
if sys.version_info[0] == 2:
return s.decode('utf-8')
return s


try:
from datetime import timezone
utc = timezone.utc
except ImportError:

class UTC(tzinfo):
def tzname(self, dt):
return 'UTC'

def utcoffset(self, dt):
return timedelta(0)

def dst(self, dt):
return None

utc = UTC()
return isinstance(value, Iterable) and not isinstance(value, (str, dict))


def timestamp(x):
"""Get a timestamp from a date in python 3 and python 2"""
"""Get a timestamp from a date"""
if x.tzinfo is None:
# Naive dates to utc
x = x.replace(tzinfo=utc)
x = x.replace(tzinfo=datetime.timezone.utc)

if hasattr(x, 'timestamp'):
return x.timestamp()
else:
return (x - datetime(1970, 1, 1, tzinfo=utc)).total_seconds()
return x.timestamp()
4 changes: 1 addition & 3 deletions pygal/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@
"""Value adapters to use when a chart doesn't accept all value types"""
from decimal import Decimal

from pygal._compat import is_str


def positive(x):
"""Return zero if value is negative"""
if x is None:
return
if is_str(x):
if isinstance(x, str):
return x
if x < 0:
return 0
Expand Down
7 changes: 3 additions & 4 deletions pygal/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from datetime import date, datetime, time
from math import floor, log

from pygal._compat import to_str, u
from pygal.util import float_format


Expand All @@ -35,9 +34,9 @@ class Formatter(object):

class HumanReadable(Formatter):
"""Format a number to engineer scale"""
ORDERS = u("yzafpnµm kMGTPEZY")
ORDERS = "yzafpnµm kMGTPEZY"

def __init__(self, none_char=u('∅')):
def __init__(self, none_char='∅'):
self.none_char = none_char

def __call__(self, val):
Expand Down Expand Up @@ -80,7 +79,7 @@ class Raw(Formatter):
def __call__(self, val):
if val is None:
return ''
return to_str(val)
return str(val)


class IsoDateTime(Formatter):
Expand Down
20 changes: 11 additions & 9 deletions pygal/graph/dot.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

from math import log10

from pygal._compat import to_str
from pygal.graph.graph import Graph
from pygal.util import alter, cached_property, decorate, safe_enumerate
from pygal.view import ReverseView, View
Expand Down Expand Up @@ -89,15 +88,18 @@ def _compute(self):
for i in range(x_len)]

def _compute_y_labels(self):
self._y_labels = list(
zip(
self.y_labels and map(to_str, self.y_labels) or [
if self.y_labels:
y_labels = [str(label) for label in self.y_labels]
else:
y_labels = [
(
serie.title['title']
if isinstance(serie.title, dict) else serie.title or ''
for serie in self.series
], self._y_pos
)
)
if isinstance(serie.title, dict)
else serie.title
) or ''
for serie in self.series
]
self._y_labels = list(zip(y_labels, self._y_pos))

def _set_view(self):
"""Assign a view to current graph"""
Expand Down
3 changes: 1 addition & 2 deletions pygal/graph/dual.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""Dual chart base. Dual means a chart with 2 scaled axis like xy"""

from pygal._compat import is_str
from pygal.graph.graph import Graph
from pygal.util import compute_scale, cut

Expand All @@ -43,7 +42,7 @@ def _compute_x_labels(self):
if isinstance(x_label, dict):
pos = self._x_adapt(x_label.get('value'))
title = x_label.get('label', self._x_format(pos))
elif is_str(x_label):
elif isinstance(x_label, str):
pos = self._x_adapt(x_pos[i % len(x_pos)])
title = x_label
else:
Expand Down
3 changes: 1 addition & 2 deletions pygal/graph/gauge.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""Gauge chart representing values as needles on a polar scale"""

from pygal._compat import is_str
from pygal.graph.graph import Graph
from pygal.util import alter, compute_scale, cut, decorate
from pygal.view import PolarThetaLogView, PolarThetaView
Expand Down Expand Up @@ -149,7 +148,7 @@ def _compute_y_labels(self):
if isinstance(y_label, dict):
pos = self._adapt(y_label.get('value'))
title = y_label.get('label', self._y_format(pos))
elif is_str(y_label):
elif isinstance(y_label, str):
pos = self._adapt(y_pos[i])
title = y_label
else:
Expand Down
14 changes: 7 additions & 7 deletions pygal/graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from math import ceil, cos, sin, sqrt

from pygal import stats
from pygal._compat import is_list_like, is_str, to_str
from pygal._compat import is_list_like
from pygal.graph.public import PublicApi
from pygal.interpolate import INTERPOLATIONS
from pygal.util import (
Expand Down Expand Up @@ -555,10 +555,10 @@ def _tooltip_data(self, node, value, x, y, classes=None, xlabel=None):
classes.append('top')
classes = ' '.join(classes)

self.svg.node(node, 'desc', class_="x " + classes).text = to_str(x)
self.svg.node(node, 'desc', class_="y " + classes).text = to_str(y)
self.svg.node(node, 'desc', class_="x " + classes).text = str(x)
self.svg.node(node, 'desc', class_="y " + classes).text = str(y)
if xlabel:
self.svg.node(node, 'desc', class_="x_label").text = to_str(xlabel)
self.svg.node(node, 'desc', class_="x_label").text = str(xlabel)

def _static_value(
self,
Expand Down Expand Up @@ -659,7 +659,7 @@ def _x_format(self):

@property
def _default_formatter(self):
return to_str
return str

@property
def _y_format(self):
Expand Down Expand Up @@ -907,7 +907,7 @@ def _order(self):
return len(self.all_series)

def _x_label_format_if_value(self, label):
if not is_str(label):
if not isinstance(label, str):
return self._x_format(label)
return label

Expand Down Expand Up @@ -953,7 +953,7 @@ def _compute_y_labels(self):
if isinstance(y_label, dict):
pos = self._adapt(y_label.get('value'))
title = y_label.get('label', self._y_format(pos))
elif is_str(y_label):
elif isinstance(y_label, str):
pos = self._adapt(y_pos[i % len(y_pos)])
title = y_label
else:
Expand Down
12 changes: 6 additions & 6 deletions pygal/graph/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import base64
import io

from pygal._compat import _ellipsis, is_list_like, u
from pygal._compat import is_list_like
from pygal.graph.base import BaseGraph


Expand Down Expand Up @@ -122,14 +122,14 @@ def render_to_png(self, filename=None, dpi=72, **kwargs):

def render_sparktext(self, relative_to=None):
"""Make a mini text sparkline from chart"""
bars = u('▁▂▃▄▅▆▇█')
bars = '▁▂▃▄▅▆▇█'
if len(self.raw_series) == 0:
return u('')
return ''
values = list(self.raw_series[0][0])
if len(values) == 0:
return u('')
return ''

chart = u('')
chart = ''
values = list(map(lambda x: max(x, 0), values))

vmax = max(values)
Expand Down Expand Up @@ -163,7 +163,7 @@ def render_sparkline(self, **kwargs):
explicit_size=True,
no_data_text='',
js=(),
classes=(_ellipsis, 'pygal-sparkline')
classes=(Ellipsis, 'pygal-sparkline'),
)
spark_options.update(kwargs)
return self.render(**spark_options)
3 changes: 1 addition & 2 deletions pygal/graph/radar.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

from math import cos, pi

from pygal._compat import is_str
from pygal.adapters import none_to_zero, positive
from pygal.graph.line import Line
from pygal.util import cached_property, compute_scale, cut, deg, truncate
Expand Down Expand Up @@ -200,7 +199,7 @@ def _compute_y_labels(self):
if isinstance(y_label, dict):
pos = self._adapt(y_label.get('value'))
title = y_label.get('label', self._y_format(pos))
elif is_str(y_label):
elif isinstance(y_label, str):
pos = self._adapt(y_pos[i])
title = y_label
else:
Expand Down
4 changes: 2 additions & 2 deletions pygal/graph/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from datetime import date, datetime, time, timedelta

from pygal._compat import is_str, timestamp
from pygal._compat import timestamp
from pygal.adapters import positive
from pygal.graph.xy import XY

Expand Down Expand Up @@ -69,7 +69,7 @@ def time_to_seconds(x):
return ((((x.hour * 60) + x.minute) * 60 + x.second) * 10**6 +
x.microsecond) / 10**6

if is_str(x):
if isinstance(x, str):
return x
# Clamp to valid time
return x and max(0, min(x, 24 * 3600 - 10**-6))
Expand Down
17 changes: 7 additions & 10 deletions pygal/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
from urllib.parse import quote_plus

from pygal import __version__
from pygal._compat import to_str, u
from pygal.etree import etree
from pygal.util import (
coord_abs_project,
Expand Down Expand Up @@ -73,16 +72,14 @@ def __init__(self, graph):
self.root.attrib['class'] = ' '.join(graph.classes)
self.root.append(
etree.Comment(
u(
'Generated with pygal %s (%s) ©Kozea 2012-2016 on %s' % (
__version__, 'lxml' if etree.lxml else 'etree',
date.today().isoformat()
)
'Generated with pygal %s (%s) ©Kozea 2012-2016 on %s' % (
__version__, 'lxml' if etree.lxml else 'etree',
date.today().isoformat()
)
)
)
self.root.append(etree.Comment(u('http://pygal.org')))
self.root.append(etree.Comment(u('http://github.com/Kozea/pygal')))
self.root.append(etree.Comment('http://pygal.org'))
self.root.append(etree.Comment('http://github.com/Kozea/pygal'))
self.defs = self.node(tag='defs')
self.title = self.node(tag='title')
self.title.text = graph.title or 'Pygal'
Expand Down Expand Up @@ -130,7 +127,7 @@ def add_styles(self):
if css.startswith('//') and self.graph.force_uri_protocol:
css = '%s:%s' % (self.graph.force_uri_protocol, css)
self.processing_instructions.append(
etree.PI(u('xml-stylesheet'), u('href="%s"' % css))
etree.PI('xml-stylesheet', 'href="%s"' % css)
)
self.node(
self.defs, 'style', type='text/css'
Expand Down Expand Up @@ -201,7 +198,7 @@ def in_attrib_and_number(key):
if value is None:
del attrib[key]

attrib[key] = to_str(value)
attrib[key] = str(value)
if key.endswith('_'):
attrib[key.rstrip('_')] = attrib[key]
del attrib[key]
Expand Down
Loading