mirror of https://github.com/mahmoud/boltons.git
Table docstrings
This commit is contained in:
parent
7c29c96422
commit
cc809fc95e
|
@ -1,4 +1,23 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
"""If there is one recurring theme in ``boltons``, it is that Python
|
||||||
|
has excellent datastructures that constitute a good foundation for
|
||||||
|
most quick manipulations, as well as building applications. However,
|
||||||
|
Python usage has grown much faster than builtin data structure
|
||||||
|
power. Python has a growing need for more advanced general-purpose
|
||||||
|
data structures which behave intuitively.
|
||||||
|
|
||||||
|
The :class:`Table` class is one example. When handed one- or
|
||||||
|
two-dimensional data, it can provide useful, if basic, text and HTML
|
||||||
|
renditions of small to medium sized data. It also heuristically
|
||||||
|
handles recursive data of various formats (lists, dicts, namedtuples,
|
||||||
|
objects).
|
||||||
|
|
||||||
|
For more advanced Table-style manipulation check out the `pandas`_
|
||||||
|
DataFrame.
|
||||||
|
|
||||||
|
.. _pandas: http://pandas.pydata.org/
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
import cgi
|
import cgi
|
||||||
import types
|
import types
|
||||||
|
@ -12,7 +31,7 @@ except ImportError:
|
||||||
_MISSING = object()
|
_MISSING = object()
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Some idle thoughts:
|
Some idle feature thoughts:
|
||||||
|
|
||||||
* shift around column order without rearranging data
|
* shift around column order without rearranging data
|
||||||
* gotta make it so you can add additional items, not just initialize with
|
* gotta make it so you can add additional items, not just initialize with
|
||||||
|
@ -25,8 +44,12 @@ Some idle thoughts:
|
||||||
* Would be nice to support different backends (currently uses lists
|
* Would be nice to support different backends (currently uses lists
|
||||||
exclusively). Sometimes large datasets come in list-of-dicts and
|
exclusively). Sometimes large datasets come in list-of-dicts and
|
||||||
list-of-tuples format and it's desirable to cut down processing overhead.
|
list-of-tuples format and it's desirable to cut down processing overhead.
|
||||||
|
|
||||||
|
TODO: make iterable on rows?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__all__ = ['Table']
|
||||||
|
|
||||||
|
|
||||||
def to_text(obj, maxlen=None):
|
def to_text(obj, maxlen=None):
|
||||||
try:
|
try:
|
||||||
|
@ -87,7 +110,7 @@ class ObjectInputType(InputType):
|
||||||
def guess_headers(self, obj):
|
def guess_headers(self, obj):
|
||||||
headers = []
|
headers = []
|
||||||
for attr in dir(obj):
|
for attr in dir(obj):
|
||||||
# an object's __dict__ could have non-string keys but meh
|
# an object's __dict__ could technically have non-string keys
|
||||||
try:
|
try:
|
||||||
val = getattr(obj, attr)
|
val = getattr(obj, attr)
|
||||||
except:
|
except:
|
||||||
|
@ -110,8 +133,8 @@ class ObjectInputType(InputType):
|
||||||
|
|
||||||
|
|
||||||
# might be better to hardcode list support since it's so close to the
|
# might be better to hardcode list support since it's so close to the
|
||||||
# core or might be better to make this the copy-style from_* importer
|
# core or might be better to make this the copy-style from_* importer
|
||||||
# and have the non-copy style be hardcoded in __init__
|
# and have the non-copy style be hardcoded in __init__
|
||||||
class ListInputType(InputType):
|
class ListInputType(InputType):
|
||||||
def check_type(self, obj):
|
def check_type(self, obj):
|
||||||
return isinstance(obj, MutableSequence)
|
return isinstance(obj, MutableSequence)
|
||||||
|
@ -180,7 +203,7 @@ class Table(object):
|
||||||
* dict (list/single)
|
* dict (list/single)
|
||||||
* object (list/single)
|
* object (list/single)
|
||||||
* namedtuple (list/single)
|
* namedtuple (list/single)
|
||||||
* TODO: sqlite return value
|
* TODO: DB API cursor?
|
||||||
* TODO: json
|
* TODO: json
|
||||||
|
|
||||||
Supported outputs:
|
Supported outputs:
|
||||||
|
@ -190,6 +213,8 @@ class Table(object):
|
||||||
* TODO: CSV
|
* TODO: CSV
|
||||||
* TODO: json
|
* TODO: json
|
||||||
* TODO: json lines
|
* TODO: json lines
|
||||||
|
|
||||||
|
To minimize resident size, the Table data is stored as a list of lists.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# order definitely matters here
|
# order definitely matters here
|
||||||
|
@ -215,6 +240,9 @@ class Table(object):
|
||||||
self.extend(data)
|
self.extend(data)
|
||||||
|
|
||||||
def extend(self, data):
|
def extend(self, data):
|
||||||
|
"""
|
||||||
|
Append the given data to the end of the Table.
|
||||||
|
"""
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
self._data.extend(data)
|
self._data.extend(data)
|
||||||
|
@ -243,21 +271,57 @@ class Table(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data, headers=_MISSING, max_depth=1):
|
def from_dict(cls, data, headers=_MISSING, max_depth=1):
|
||||||
|
"""\
|
||||||
|
Create a Table from a :class:`dict`. Operates the same as
|
||||||
|
:meth:`from_data`, but forces interpretation of the data as a
|
||||||
|
Mapping.
|
||||||
|
"""
|
||||||
return cls.from_data(data=data, headers=headers,
|
return cls.from_data(data=data, headers=headers,
|
||||||
max_depth=max_depth, _data_type=DictInputType())
|
max_depth=max_depth, _data_type=DictInputType())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, data, headers=_MISSING, max_depth=1):
|
def from_list(cls, data, headers=_MISSING, max_depth=1):
|
||||||
|
"""\
|
||||||
|
Create a Table from a :class:`list`. Operates the same as
|
||||||
|
:meth:`from_data`, but forces the interpretation of the data
|
||||||
|
as a Sequence.
|
||||||
|
"""
|
||||||
return cls.from_data(data=data, headers=headers,
|
return cls.from_data(data=data, headers=headers,
|
||||||
max_depth=max_depth, _data_type=ListInputType())
|
max_depth=max_depth, _data_type=ListInputType())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_object(cls, data, headers=_MISSING, max_depth=1):
|
def from_object(cls, data, headers=_MISSING, max_depth=1):
|
||||||
|
"""\
|
||||||
|
Create a Table from an :class:`object`. Operates the same as
|
||||||
|
:meth:`from_data`, but forces the interpretation of the data
|
||||||
|
as an object. May be useful for some :class:`dict` and
|
||||||
|
:class:`list` subtypes.
|
||||||
|
"""
|
||||||
return cls.from_data(data=data, headers=headers,
|
return cls.from_data(data=data, headers=headers,
|
||||||
max_depth=max_depth, _data_type=ObjectInputType())
|
max_depth=max_depth, _data_type=ObjectInputType())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_data(cls, data, headers=_MISSING, max_depth=1, _data_type=None):
|
def from_data(cls, data, headers=_MISSING, max_depth=1, _data_type=None):
|
||||||
|
"""\
|
||||||
|
Create a Table from any supported data, heuristically
|
||||||
|
selecting how to represent the data in Table format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
data (object): Any object or iterable with data to be
|
||||||
|
imported to the Table.
|
||||||
|
|
||||||
|
headers (iterable): An iterable of headers to be matched
|
||||||
|
to the data. If not explicitly passed, headers will be
|
||||||
|
guessed for certain datatypes.
|
||||||
|
|
||||||
|
max_depth (int): The level to which nested Tables should
|
||||||
|
be created (default: 1).
|
||||||
|
|
||||||
|
_data_type (InputType subclass): For advanced use cases,
|
||||||
|
do not guess the type of the input data, use this data
|
||||||
|
type instead.
|
||||||
|
"""
|
||||||
# TODO: seen/cycle detection/reuse ?
|
# TODO: seen/cycle detection/reuse ?
|
||||||
# maxdepth follows the same behavior as find command
|
# maxdepth follows the same behavior as find command
|
||||||
# i.e., it doesn't work if max_depth=0 is passed in
|
# i.e., it doesn't work if max_depth=0 is passed in
|
||||||
|
@ -326,6 +390,29 @@ class Table(object):
|
||||||
|
|
||||||
def to_html(self, orientation=None, wrapped=True,
|
def to_html(self, orientation=None, wrapped=True,
|
||||||
with_headers=True, with_newlines=True, max_depth=1):
|
with_headers=True, with_newlines=True, max_depth=1):
|
||||||
|
"""\
|
||||||
|
Render this Table to HTML. Configure the structure of Table
|
||||||
|
HTML by subclassing and overriding ``_html_*`` class
|
||||||
|
attributes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
orientation (str): one of 'auto', 'horizontal', or
|
||||||
|
'vertical' (or the first letter of any of
|
||||||
|
those). Default 'auto'.
|
||||||
|
wrapped (bool): whether or not to include the wrapping
|
||||||
|
'<table></table>' tags. Default ``True``, set to
|
||||||
|
``False`` if appending multiple Table outputs or an
|
||||||
|
otherwise customized HTML wrapping tag is needed.
|
||||||
|
with_newlines (bool): Set to ``True`` if output should
|
||||||
|
include added newlines to make the HTML more
|
||||||
|
readable. Default ``False``.
|
||||||
|
max_depth (int): Indicate how deeply to nest HTML tables
|
||||||
|
before simply reverting to :func:`repr`-ing the nested
|
||||||
|
data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A text string of the HTML of the rendered table.
|
||||||
|
"""
|
||||||
lines = []
|
lines = []
|
||||||
headers = []
|
headers = []
|
||||||
if with_headers and self.headers:
|
if with_headers and self.headers:
|
||||||
|
@ -401,6 +488,14 @@ class Table(object):
|
||||||
lines.append(''.join(line_parts))
|
lines.append(''.join(line_parts))
|
||||||
|
|
||||||
def to_text(self, with_headers=True, maxlen=None):
|
def to_text(self, with_headers=True, maxlen=None):
|
||||||
|
"""\
|
||||||
|
Get the Table's textual representation. Only works well
|
||||||
|
for Tables with non-recursive data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
with_headers (bool): Whether to include a header row at the top.
|
||||||
|
maxlen (int): Max length of data in each cell.
|
||||||
|
"""
|
||||||
# TODO: verify this works for markdown
|
# TODO: verify this works for markdown
|
||||||
lines = []
|
lines = []
|
||||||
widths = []
|
widths = []
|
||||||
|
@ -422,29 +517,27 @@ class Table(object):
|
||||||
return '\n'.join(lines)
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
global t3
|
|
||||||
data_dicts = [{'id': 1, 'name': 'John Doe'},
|
|
||||||
{'id': 2, 'name': 'Dale Simmons'}]
|
|
||||||
data_lists = [['id', 'name'],
|
|
||||||
[1, 'John Doe'],
|
|
||||||
[2, 'Dale Simmons']]
|
|
||||||
t1 = Table(data_lists)
|
|
||||||
t2 = Table.from_dict(data_dicts[0])
|
|
||||||
t3 = Table.from_dict(data_dicts)
|
|
||||||
t3.extend([[3, 'Kurt Rose'], [4]])
|
|
||||||
print t1
|
|
||||||
print t2
|
|
||||||
print t2.to_html()
|
|
||||||
print t3
|
|
||||||
print t3.to_html()
|
|
||||||
print t3.to_text()
|
|
||||||
|
|
||||||
import re
|
|
||||||
t4 = Table.from_object(re.compile(''))
|
|
||||||
print t4.to_text()
|
|
||||||
import pdb;pdb.set_trace()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
def main():
|
||||||
|
data_dicts = [{'id': 1, 'name': 'John Doe'},
|
||||||
|
{'id': 2, 'name': 'Dale Simmons'}]
|
||||||
|
data_lists = [['id', 'name'],
|
||||||
|
[1, 'John Doe'],
|
||||||
|
[2, 'Dale Simmons']]
|
||||||
|
t1 = Table(data_lists)
|
||||||
|
t2 = Table.from_dict(data_dicts[0])
|
||||||
|
t3 = Table.from_dict(data_dicts)
|
||||||
|
t3.extend([[3, 'Kurt Rose'], [4]])
|
||||||
|
print t1
|
||||||
|
print t2
|
||||||
|
print t2.to_html()
|
||||||
|
print t3
|
||||||
|
print t3.to_html()
|
||||||
|
print t3.to_text()
|
||||||
|
|
||||||
|
import re
|
||||||
|
t4 = Table.from_object(re.compile(''))
|
||||||
|
print t4.to_text()
|
||||||
|
import pdb;pdb.set_trace()
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Reference in New Issue