├── .gitignore ├── .travis.yml ├── CHANGELOG.rst ├── LICENSE ├── README.rst ├── dev_requirements.txt ├── ipy_table ├── __init__.py ├── ipy_table.py ├── six.py ├── vector_manager.py └── version.py ├── notebooks ├── add_parent_to_path.py ├── ipy_table-Introduction.ipynb ├── ipy_table-MakeTestVectors.ipynb ├── ipy_table-Reference.ipynb ├── ipy_table-Test.ipynb └── ipy_table-VerifyTestVectors.ipynb ├── requirements.txt ├── setup.py └── test ├── __init__.py ├── test_vectors.json └── test_vectors.py /.gitignore: -------------------------------------------------------------------------------- 1 | #------------------ 2 | # Caches 3 | #------------------ 4 | .cache 5 | *.pyc 6 | .idea 7 | # macOS file-system supporting data 8 | .DS_Store 9 | 10 | .ipynb_checkpoints/ 11 | .project 12 | .pydevproject 13 | *~ 14 | release_images 15 | dist/ 16 | build/ 17 | ipy_table.egg-info/ 18 | candidate_test_vectors.json 19 | notebooks/adopt_vectors.sh 20 | notebooks/ipy_table-VerifyTestVectorsPython3.ipynb 21 | 22 | # Working directory for untracked files 23 | untracked/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.org 2 | 3 | language: python 4 | python: 5 | - 2.7 6 | - 3.3 7 | - 3.4 8 | - 3.5 9 | - 3.6 10 | install: 11 | - pip install -r dev_requirements.txt 12 | cache: pip 13 | script: 14 | - py.test -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on `Keep a Changelog`_ and this project adheres to `Semantic Versioning`_. 7 | 8 | .. _Keep a Changelog: http://keepachangelog.com/en/1.0.0/ 9 | .. _Semantic Versioning: http://semver.org/spec/v2.0.0.html 10 | 11 | Unreleased 12 | ---------- 13 | 14 | (None) 15 | 16 | 1.15.1 - 2017-Aug-25 17 | -------------------- 18 | 19 | Fixed 20 | ^^^^^ 21 | - fix packages in setup.py 22 | 23 | 1.15.0 - 2017-Aug-25 24 | -------------------- 25 | 26 | Changed 27 | ^^^^^^^ 28 | 29 | - Compatibility now Python 2.7, 3.3, 3.4, 3.5, 3.6 30 | - Defaulted to solid cell borders, so that table behavior remains consistent in the current version of Jupyter. 31 | 32 | - Jupyter 4.3.0 now defaults to invisible cell borders, though I am not sure in which version the change was first made 33 | - Adopted semantic versioning 34 | 35 | - Planned next release will be ``1.15.0`` 36 | 37 | - Moved source into ``ipy_table`` module directory 38 | - Moved history from README.rst to CHANGELOG.rst 39 | 40 | Fixed 41 | ^^^^^ 42 | - Use `` `` instead of `` `` 43 | 44 | Added 45 | ^^^^^ 46 | - Tests (using `py.test`) 47 | 48 | - Test vector generator notebook: ``ipy_table-MakeTestVectors.ipynb`` 49 | - Test vector failure visualization notebook: ``ipy_table-VerifyTestVectors.ipynb`` 50 | - numpy is required for testing, but not for general ipy_table installation/use 51 | 52 | 1.14 - 2017-Aug-24 53 | ------------------ 54 | 55 | - Fix email format in setup.py 56 | 57 | 1.13 - 2013-Dec-31 58 | ------------------ 59 | 60 | - Fix Unicode bug. Unicode can now be used in cell contents. 61 | - Example added to ipy_table-Test.ipynb. Thanks JoshRosen for the find! 62 | 63 | 1.12 - 2013-Jan-6 64 | ----------------- 65 | 66 | - Adopt the standard IPython display protocol. Instead of returning an Ipython.core.display.HTML object, add the _repr_html_() method to the IpyTable class. 67 | - Remove the get_table_html() method (no longer necessary; the table HTML can now be obtained by calling _repr_html_() explicitly). 68 | - Remove the render() method from IpyTable (no longer necessary). 69 | - Remove the get_table_html() function (no longer necessary; can call render()._repr_html() in interactive mode). 70 | 71 | 1.11 - 2012-Dec-30 72 | ------------------ 73 | 74 | - Initial GitHub release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ============================== 2 | The ipy_table licensing terms 3 | ============================== 4 | 5 | ipy_table is licensed under the terms of the Modified BSD License (also known as 6 | New or Revised BSD), as follows: 7 | 8 | Copyright (c) 2012-2017, Eric Moyer 9 | 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without 13 | modification, are permitted provided that the following conditions are met: 14 | 15 | Redistributions of source code must retain the above copyright notice, this 16 | list of conditions and the following disclaimer. 17 | 18 | Redistributions in binary form must reproduce the above copyright notice, this 19 | list of conditions and the following disclaimer in the documentation and/or 20 | other materials provided with the distribution. 21 | 22 | Neither the name of the ipy_table Development Team nor the names of its 23 | contributors may be used to endorse or promote products derived from this 24 | software without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 27 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 28 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | 37 | About the ipy_table Development Team 38 | ------------------------------------ 39 | 40 | Eric Moyer is the project lead. 41 | 42 | The ipy_table Development Team is the set of all contributors to the ipy_table 43 | project. 44 | 45 | The core team that coordinates development on GitHub can be found here: 46 | http://github.com/epmoyer/ipy_table. As of late 2012, it consists of: 47 | 48 | * Eric Moyer 49 | 50 | 51 | Our Copyright Policy 52 | -------------------- 53 | 54 | ipy_table uses a shared copyright model. Each contributor maintains copyright 55 | over their contributions to ipy_table. But, it is important to note that these 56 | contributions are typically only changes to the repositories. Thus, the ipy_table 57 | source code, in its entirety is not the copyright of any single person or 58 | institution. Instead, it is the collective copyright of the entire ipy_table 59 | Development Team. If individual contributors want to maintain a record of what 60 | changes/contributions they have specific copyright on, they should indicate 61 | their copyright in the commit message of the change, when they commit the 62 | change to one of the ipy_table repositories. 63 | 64 | With this in mind, the following banner should be used in any source code file 65 | to indicate the copyright and license terms: 66 | 67 | #----------------------------------------------------------------------------- 68 | # Copyright (c) 2012-2017, ipy_table Development Team. 69 | # 70 | # Distributed under the terms of the Modified BSD License. 71 | # 72 | # The full license is in the file LICENSE, distributed with this software. 73 | #----------------------------------------------------------------------------- 74 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | :author: `Eric Moyer`_ 2 | :copyright: Copyright © 2012-2017 Eric Moyer 3 | :license: Modified BSD 4 | 5 | ######### 6 | ipy_table 7 | ######### 8 | 9 | |Build Status| |Coverage| |PyPIVersion| |Tag| 10 | 11 | Overview 12 | ======== 13 | 14 | ``ipy_table`` is a support module for Jupyter Notebooks. ipy_table is an independent project and is not an official component of the Jupyter package. 15 | 16 | The home page for ``ipy_table`` is http://epmoyer.github.com/ipy_table/ 17 | 18 | ``ipy_table`` is maintained at http://github.com/epmoyer/ipy_table 19 | 20 | IPython is maintained at http://github.com/ipython, and documentation is available from http://ipython.org/ 21 | 22 | Jupyter is maintained at https://github.com/jupyter, and documentation is available at http://jupyter.org/ 23 | 24 | Documentation 25 | ============= 26 | 27 | Documentation is provided by the documentation notebooks supplied with this package:: 28 | 29 | notebooks/ipy_table-Introduction.ipynb 30 | notebooks/ipy_table-Reference.ipynb 31 | 32 | The documentation notebooks can be viewed online with nbviewer at Introduction_ and Reference_. 33 | 34 | Dependencies and Supported Python Versions 35 | ========================================== 36 | 37 | ``ipy_table`` works with Python 2.7, 3.3, 3.4, 3.5, and 3.6 38 | 39 | ``ipy_table`` is designed to be used within a Jupyter Notebook. 40 | 41 | IPython qtconsole operation is not currently officially supported. ``ipy_table`` renders tables using HTML, and HTML tables render differently in the IPython qtconsole than in a Jupyter notebook for reasons which I have not yet unraveled. Particularly, cell border rendering behaves differently. 42 | 43 | Installation 44 | ============ 45 | 46 | 1) Run: ``pip install ipy_table`` 47 | 48 | 2) Copy the documentation notebooks (``notebooks/ipy_table-Introduction.ipynb`` and ``notebooks/ipy_table-Reference.ipynb``) to your main Jupyter notebook working directory (the directory where your Jupyter notebooks are stored). 49 | 50 | If you don't know your Jupyter notebook working directory, start the Jupyter Notebook server, create a blank notebook, and execute the command 'pwd'. 51 | 52 | Testing 53 | ======= 54 | 55 | To execute the tests, run ``py.test`` from the project root directory 56 | NumPy (``numpy``) is a dependency for running the tests, but is not a dependency for installing / using ``ipy_table`` 57 | 58 | Contributors 59 | ============ 60 | 61 | Ordered by date of first contribution 62 | 63 | - `Eric Moyer `_ aka ``epmoyer`` 64 | - `Matthias Bussonnier `_ aka ``Carreau`` 65 | - `Josh Rosen `_ aka ``JoshRosen`` 66 | - `Dominic R. May `_ aka ``Mause`` 67 | - `Francisco J Lopez-Pellicer `_ aka ``fjlopez`` 68 | - `jhykes `_ 69 | - `Jack Lamberti `_ aka ``jamlamberti`` 70 | 71 | 72 | .. _`Eric Moyer`: mailto:eric@lemoncrab.com 73 | .. _Introduction: http://nbviewer.ipython.org/urls/raw.github.com/epmoyer/ipy_table/master/notebooks/ipy_table-Introduction.ipynb 74 | .. _Reference: http://nbviewer.ipython.org/urls/raw.github.com/epmoyer/ipy_table/master/notebooks/ipy_table-Reference.ipynb 75 | .. |Build Status| image:: https://img.shields.io/travis/epmoyer/ipy_table.svg?style=flat 76 | :target: https://travis-ci.org/epmoyer/ipy_table 77 | .. |Coverage| image:: https://img.shields.io/coveralls/epmoyer/ipy_table.svg?style=flat 78 | :target: https://coveralls.io/github/epmoyer/ipy_table?branch=master 79 | .. |PyPIVersion| image:: https://img.shields.io/pypi/v/ipy_table.svg?style=flat 80 | :target: https://pypi.python.org/pypi/ipy_table/ 81 | .. |Tag| image:: https://img.shields.io/github/tag/epmoyer/ipy_table.svg?style=flat 82 | :target: https://github.com/epmoyer/ipy_table/tags -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | IPython>=1.0.0 2 | numpy>=1.9.0 3 | -------------------------------------------------------------------------------- /ipy_table/__init__.py: -------------------------------------------------------------------------------- 1 | from .ipy_table import (IpyTable, 2 | tabulate, make_table, set_cell_style, set_column_style, 3 | set_row_style, set_global_style, apply_theme, 4 | render, get_interactive_return_value 5 | ) 6 | 7 | from .vector_manager import VectorManager 8 | from .version import __version__ 9 | 10 | __all__ = ('IpyTable', 'VectorManager', 11 | 'tabulate', 'make_table', 'set_cell_style', 'set_column_style', 12 | 'set_row_style', 'set_global_style', 'apply_theme', 13 | 'render', 'get_interactive_return_value' 14 | ) -------------------------------------------------------------------------------- /ipy_table/ipy_table.py: -------------------------------------------------------------------------------- 1 | """Table formatting package for IP[y] Notebooks 2 | 3 | ipy_table is a support module for creating formatted tables in an 4 | IPython Notebook. ipy_table is an independent project and is not 5 | an official component of the IPython package. 6 | 7 | Documentation is provided by the documentation notebooks supplied with 8 | this package: 9 | ipy_table-Introduction.ipynb 10 | ipy_table-Reference.ipynb 11 | 12 | All cell, row, column, and global style formatting functions accept 13 | optional style_args. style_args support the following arguments: 14 | color= 15 | can be any any standard web/X11 color name. 16 | For a list see http://en.wikipedia.org/wiki/Web_colors 17 | bold= 18 | italic= 19 | thick_border= 20 | is a comma delimited string containing any of the 21 | keywords 'left', 'right', 'bottom' and 'top' to specify 22 | individual edges, or 'all' to specify all edges. 23 | no_border= 24 | is a comma delimited string containing any of the 25 | keywords 'left', 'right', 'bottom' and 'top' to specify 26 | individual edges, or 'all' to specify all edges. 27 | row_span= 28 | column_span= 29 | width= 30 | align= 31 | can be 'left', 'right', or 'center' 32 | wrap= 33 | float_format= 34 | is a standard Python '%' format string 35 | (e.g. '%0.6f' or '$%0.2f') 36 | 37 | Design goals: 38 | * Easy to use in an interactive IPython Notebook session. 39 | (minimal syntax, interactive modification of styles) 40 | * Maintainability 41 | (minimize the overhead of adding new style features) 42 | * Robustness over HTML verbosity 43 | (HTML styles are only manipulated at the cell level, which 44 | results in robust style flexibility and general implementation 45 | simplicity at the expense of occasional HTML verbosity. 46 | HTML row styles and table styles are never manipulated). 47 | 48 | --------------------------------------------------------------------------- 49 | Copyright (c) 2012-2017, ipy_table Development Team. 50 | 51 | Distributed under the terms of the Modified BSD License. 52 | 53 | The full license is in the file LICENSE, distributed with this software. 54 | 55 | This project is maintained at http://github.com/epmoyer/ipy_table 56 | """ 57 | 58 | import copy 59 | from collections import OrderedDict 60 | from six import string_types 61 | 62 | # Private table object used for interactive mode 63 | _TABLE = None 64 | _INTERACTIVE = True 65 | 66 | #----------------------------- 67 | # Classes 68 | #----------------------------- 69 | 70 | 71 | class IpyTable(object): 72 | 73 | _valid_borders = {'left', 'right', 'top', 'bottom', 'all'} 74 | 75 | #--------------------------------- 76 | # External methods 77 | #--------------------------------- 78 | 79 | def __init__(self, array): 80 | self.array = array 81 | 82 | self._num_rows = len(array) 83 | self._num_columns = len(array[0]) 84 | 85 | # Float type checking is performed by calling 86 | # str(type(value)) and comparing it to the 87 | # list below (to provide numpy compatibility 88 | # without having it as a dependency) 89 | self._float_types = [ 90 | # Python 2 91 | "", 92 | "", 93 | "", 94 | "", 95 | "", 96 | # Python 3 97 | "", 98 | "", 99 | "", 100 | "", 101 | "", 102 | ] 103 | 104 | # Check that array is well formed 105 | for row in array: 106 | if len(row) != self._num_columns: 107 | raise ValueError("Array rows must all be of equal length.") 108 | 109 | self._cell_styles = [[{'float_format': '%0.4f'} 110 | for dummy in range(self._num_columns)] 111 | for dummy2 in range(self._num_rows)] 112 | 113 | def _repr_html_(self): 114 | """IPython display protocol: HTML representation. 115 | 116 | The IPython display protocol calls this method to get the HTML 117 | representation of this object. 118 | """ 119 | #--------------------------------------- 120 | # Generate TABLE tag () 121 | #--------------------------------------- 122 | html = '' 124 | 125 | for row, row_data in enumerate(self.array): 126 | 127 | #--------------------------------------- 128 | # Generate ROW tag () 129 | #--------------------------------------- 130 | html += '' 131 | for (column, item) in enumerate(row_data): 132 | if not _key_is_valid( 133 | self._cell_styles[row][column], 'suppress'): 134 | 135 | #--------------------------------------- 136 | # Generate CELL tag (' 155 | html += '' 156 | html += '
) 137 | #--------------------------------------- 138 | # Apply floating point formatter to the cell contents 139 | # (if it is a float) 140 | item_html = self._formatter( 141 | item, self._cell_styles[row][column]) 142 | 143 | # Add bold and italic tags if set 144 | if _key_is_valid(self._cell_styles[row][column], 'bold'): 145 | item_html = '' + item_html + '' 146 | if _key_is_valid(self._cell_styles[row][column], 'italic'): 147 | item_html = '' + item_html + '' 148 | 149 | # Get html style string 150 | style_html = self._get_style_html( 151 | self._cell_styles[row][column]) 152 | 153 | # Append cell 154 | html += '' + item_html + '
' 157 | return html 158 | 159 | @property 160 | def themes(self): 161 | """Get list of supported formatting themes.""" 162 | return ['basic', 'basic_left', 'basic_both'] 163 | 164 | def apply_theme(self, theme_name): 165 | """Apply a formatting theme to the entire table. 166 | 167 | The list of available themes is returned by the .themes property. 168 | """ 169 | 170 | if theme_name in self.themes: 171 | # Color rows in alternating colors 172 | for row in range(len(self.array)): 173 | if row % 2: 174 | self.set_row_style(row, color='Ivory') 175 | else: 176 | self.set_row_style(row, color='AliceBlue') 177 | # Color column header 178 | if not theme_name == 'basic_left': 179 | self.set_row_style(0, bold=True, color='LightGray') 180 | # Color row header 181 | if not theme_name == 'basic': 182 | self.set_column_style(0, bold=True, color='LightGray') 183 | # Remove upper left corner cell (make white with no left 184 | # and no top border) 185 | if theme_name == 'basic_both': 186 | self.set_cell_style(0, 0, color='White', no_border='left,top') 187 | else: 188 | raise ValueError('Unknown theme "%s". Expected one of %s.' % 189 | (theme_name, str(self.themes))) 190 | 191 | def set_cell_style(self, row, column, **style_args): 192 | """Apply style(s) to a single cell.""" 193 | self._range_check(row=row, column=column) 194 | self._set_cell_style_norender(row, column, **style_args) 195 | 196 | def set_row_style(self, row, **style_args): 197 | """Apply style(s) to a table row.""" 198 | self._range_check(row=row) 199 | for column in range(self._num_columns): 200 | self._set_cell_style_norender(row, column, **style_args) 201 | 202 | def set_column_style(self, column, **style_args): 203 | """Apply style(s) to a table column.""" 204 | self._range_check(column=column) 205 | for row in range(self._num_rows): 206 | self._set_cell_style_norender(row, column, **style_args) 207 | 208 | def set_global_style(self, **style_args): 209 | """Apply style(s) to all table cells.""" 210 | for row in range(self._num_rows): 211 | for column in range(self._num_columns): 212 | self._set_cell_style_norender(row, column, **style_args) 213 | 214 | def _range_check(self, **check_args): 215 | """Range check row and/or column index 216 | 217 | Expected argyments: 218 | row = 219 | column = 220 | """ 221 | if 'row' in check_args: 222 | row = check_args['row'] 223 | if row < 0 or row >= self._num_rows: 224 | raise ValueError( 225 | 'Bad row (%d). Expected row in range 0 to %d.' % 226 | (row, self._num_rows - 1)) 227 | 228 | if 'column' in check_args: 229 | column = check_args['column'] 230 | if column < 0 or column >= self._num_columns: 231 | raise ValueError( 232 | 'Bad column (%d). Expected column in range 0 to %d.' % 233 | (column, self._num_columns - 1)) 234 | 235 | #--------------------------------- 236 | # Internal methods 237 | #--------------------------------- 238 | 239 | def _build_style_dict(self, **style_args): 240 | """Returns a cell style dictionary based on the style arguments.""" 241 | style_dict = copy.deepcopy(style_args) 242 | for border_type in ['thick_border', 'no_border']: 243 | if border_type in style_dict: 244 | border_setting = style_dict[border_type] 245 | 246 | # Type checking 247 | 248 | if not isinstance(border_setting, string_types): 249 | raise TypeError( 250 | ('%s must be a string of comma ' % border_type) + 251 | 'separated border names (e.g. "left,right")') 252 | # Value checking 253 | if (set(border_setting.replace(' ', '').split(',')) - 254 | IpyTable._valid_borders): 255 | raise ValueError( 256 | ('%s must be a string of comma ' % border_type) + 257 | 'separated border names (e.g. "left,right"). Valid ' + 258 | 'border names: %s' % 259 | str(IpyTable._valid_borders)) 260 | # Substitute all edges for 'all' 261 | if border_setting == 'all': 262 | style_dict[border_type] = 'left,right,top,bottom' 263 | 264 | return style_dict 265 | 266 | def _merge_cell_style(self, row, column, cell_style): 267 | """Merge new cell style dictionary into the old 268 | 269 | Existing items are superseded by new. 270 | """ 271 | styles = self._cell_styles[row][column] 272 | for (new_key, new_value) in cell_style.items(): 273 | if (new_key in ['thick_border', 'no_border']) and (new_key in styles): 274 | # Merge the two border lists 275 | old_borders = self._split_by_comma(styles[new_key]) 276 | new_borders = self._split_by_comma(new_value) 277 | styles[new_key] = ",".join( 278 | old_borders + list(set(new_borders) - set(old_borders))) 279 | else: 280 | styles[new_key] = new_value 281 | 282 | def _set_cell_style_norender(self, row, column, **style_args): 283 | """Apply style(s) to a single cell, without rendering.""" 284 | cell_style = self._build_style_dict(**style_args) 285 | 286 | self._merge_cell_style(row, column, cell_style) 287 | if 'row_span' in cell_style: 288 | for row in range(row + 1, row + cell_style['row_span']): 289 | self._cell_styles[row][column]['suppress'] = True 290 | if 'column_span' in cell_style: 291 | for column in range( 292 | column + 1, 293 | column + cell_style['column_span']): 294 | self._cell_styles[row][column]['suppress'] = True 295 | 296 | # If a thick right hand border was specified, then also apply it 297 | # to the left of the adjacent cell (if one exists) 298 | if ('thick_border' in cell_style 299 | and 'right' in cell_style['thick_border'] 300 | and column + 1 < self._num_columns): 301 | self._merge_cell_style( 302 | row, column + 1, 303 | self._build_style_dict(thick_border='left')) 304 | 305 | # If a clear left hand border was specified, then also apply it 306 | # to the right of the adjacent cell (if one exists) 307 | if ('no_border' in cell_style 308 | and 'left' in cell_style['no_border'] 309 | and column > 0): 310 | self._merge_cell_style( 311 | row, column - 1, 312 | self._build_style_dict(no_border='right')) 313 | 314 | # If a thick bottom border was specified, then also apply it to 315 | # the top of the adjacent cell (if one exists) 316 | if ('thick_border' in cell_style 317 | and 'bottom' in cell_style['thick_border'] 318 | and row + 1 < self._num_rows): 319 | self._merge_cell_style( 320 | row + 1, column, 321 | self._build_style_dict(thick_border='top')) 322 | 323 | # If a clear top border was specified, then also apply it to 324 | # the bottom of the adjacent cell (if one exists) 325 | if ('no_border' in cell_style 326 | and 'top' in cell_style['no_border'] 327 | and row > 0): 328 | self._merge_cell_style( 329 | row - 1, column, 330 | self._build_style_dict(no_border='bottom')) 331 | 332 | def _get_style_html(self, style_dict): 333 | """Parse the style dictionary and return equivalent html style text.""" 334 | style_html = '' 335 | if _key_is_valid(style_dict, 'color'): 336 | style_html += 'background-color:' + style_dict['color'] + ';' 337 | 338 | # Default to 1px solid. Style settings for 'thick_border' 339 | # and 'no_border' will modify these defaults. 340 | edges = OrderedDict() 341 | for edge_name in ('left', 'right', 'top', 'bottom'): 342 | edges[edge_name] = dict(thickness=1, color='solid') 343 | 344 | if _key_is_valid(style_dict, 'thick_border'): 345 | for edge_name in self._split_by_comma(style_dict['thick_border']): 346 | edges[edge_name]['thickness'] = 3 347 | edges[edge_name]['color'] = 'solid' 348 | 349 | if _key_is_valid(style_dict, 'no_border'): 350 | for edge_name in self._split_by_comma(style_dict['no_border']): 351 | edges[edge_name]['thickness'] = 1 352 | edges[edge_name]['color'] = 'transparent' 353 | 354 | for edge_name, edge_properties in edges.items(): 355 | style_html += 'border-{}: {}px {};'.format( 356 | edge_name, edge_properties['thickness'], edge_properties['color']) 357 | 358 | if _key_is_valid(style_dict, 'align'): 359 | style_html += 'text-align:' + str(style_dict['align']) + ';' 360 | 361 | if _key_is_valid(style_dict, 'width'): 362 | style_html += 'width:' + str(style_dict['width']) + 'px;' 363 | 364 | if style_html: 365 | style_html = ' style="' + style_html + '"' 366 | 367 | if _key_is_valid(style_dict, 'row_span'): 368 | style_html = 'rowspan="' + str(style_dict['row_span']) + \ 369 | '";' + style_html 370 | 371 | if _key_is_valid(style_dict, 'column_span'): 372 | style_html = 'colspan="' + str(style_dict['column_span']) + \ 373 | '";' + style_html 374 | 375 | # Prepend a space if non-blank 376 | if style_html: 377 | return ' ' + style_html 378 | return '' 379 | 380 | def _formatter(self, item, cell_style): 381 | """Apply formatting to cell contents. 382 | 383 | Applies float format to item if item is a float (or numpy float). 384 | Converts spaces to non-breaking if wrap is not enabled. 385 | Returns string. 386 | """ 387 | if _is_float_type(item) and 'float_format' in cell_style: 388 | text = cell_style['float_format'] % item 389 | else: 390 | if isinstance(item, string_types): 391 | text = item 392 | else: 393 | text = str(item) 394 | 395 | # If cell wrapping is not specified 396 | if not ('wrap' in cell_style and cell_style['wrap']): 397 | # Convert all spaces to non-breaking and return 398 | text = text.replace(' ', ' ') 399 | return text 400 | 401 | def _split_by_comma(self, comma_delimited_text): 402 | """Returns a list of the words in the comma delimited text.""" 403 | return comma_delimited_text.replace(' ', '').split(',') 404 | 405 | #----------------------------- 406 | # Public functions 407 | #----------------------------- 408 | 409 | 410 | def tabulate(data_list, columns, interactive=True): 411 | """Renders a list (not array) of items into an HTML table.""" 412 | global _TABLE 413 | global _INTERACTIVE 414 | 415 | _INTERACTIVE = interactive 416 | total_items = len(data_list) 417 | rows = int(total_items / columns) 418 | if total_items % columns: 419 | rows += 1 420 | num_blank_cells = rows * columns - total_items 421 | if num_blank_cells: 422 | rows += 1 423 | 424 | # Create an array and pad the ending cells with null strings 425 | array = copy.copy(_convert_to_list(data_list)) 426 | pad_cells = ['' for dummy in range(num_blank_cells)] 427 | array = array + pad_cells 428 | array = [array[x:x + columns] for x in range(0, len(array), columns)] 429 | 430 | # Render the array 431 | _TABLE = IpyTable(array) 432 | return get_interactive_return_value() 433 | 434 | 435 | def make_table(array, interactive=True): 436 | """Create a table in interactive mode.""" 437 | global _TABLE 438 | global _INTERACTIVE 439 | _TABLE = IpyTable(array) 440 | _INTERACTIVE = interactive 441 | return get_interactive_return_value() 442 | 443 | 444 | def set_cell_style(row, column, **style_args): 445 | """Apply style(s) to a single cell.""" 446 | global _TABLE 447 | _TABLE.set_cell_style(row, column, **style_args) 448 | return get_interactive_return_value() 449 | 450 | 451 | def set_column_style(column, **style_args): 452 | """Apply style(s) to a table column.""" 453 | global _TABLE 454 | _TABLE.set_column_style(column, **style_args) 455 | return get_interactive_return_value() 456 | 457 | 458 | def set_row_style(row, **style_args): 459 | """Apply style(s) to a table row.""" 460 | global _TABLE 461 | _TABLE.set_row_style(row, **style_args) 462 | return get_interactive_return_value() 463 | 464 | 465 | def set_global_style(**style_args): 466 | """Apply style(s) to all table cells.""" 467 | global _TABLE 468 | _TABLE.set_global_style(**style_args) 469 | return get_interactive_return_value() 470 | 471 | 472 | def apply_theme(style_name): 473 | """Apply a formatting theme to the entire table. 474 | 475 | The list of available themes is returned by the .themes property of 476 | an IpyTable object. 477 | """ 478 | global _TABLE 479 | _TABLE.apply_theme(style_name) 480 | return get_interactive_return_value() 481 | 482 | 483 | def render(): 484 | """Render the current table. Returns the global IpyTable object instance""" 485 | global _TABLE 486 | return _TABLE 487 | 488 | 489 | def get_interactive_return_value(): 490 | """Generates the return value for all interactive functions. 491 | 492 | The interactive functions (make_table(), set_cell_style(), etc.) can 493 | be used instead of the class interface to build up a table and 494 | interactively modify it's style, rendering the new table on each call. 495 | 496 | By default all interactive functions return the working global 497 | IpyTable object (_TABLE), which typically gets rendered by IPython. 498 | That behavior can be suppressed by setting interactive=False 499 | when creating a new table with make_table() or tabulate(). If 500 | interactive rendering is suppressed then all interactive functions will 501 | return None, and rendering can be achieved explicitly by calling 502 | render() (which will always return the working global IpyTable object). 503 | """ 504 | global _INTERACTIVE 505 | global _TABLE 506 | if _INTERACTIVE: 507 | return _TABLE 508 | else: 509 | return None 510 | 511 | #----------------------------- 512 | # Private functions 513 | #----------------------------- 514 | 515 | _FLOAT_TYPES = [ 516 | # Python 2 517 | "", 518 | "", 519 | "", 520 | "", 521 | "", 522 | "", 523 | # Python 3 524 | "", 525 | "" 526 | "", 527 | "", 528 | "", 529 | "", 530 | "", 531 | ] 532 | 533 | def _is_float_type(value): 534 | ''' True if type(value) is one of several float types 535 | 536 | Float type checking is performed by calling 537 | str(type(value)) and comparing it to known string 538 | representations of float types to provide numpy 539 | compatibility without having numpy as a dependency 540 | ''' 541 | return str(type(value)) in _FLOAT_TYPES 542 | 543 | 544 | def _convert_to_list(data): 545 | """Accepts a list or a numpy.ndarray and returns a list.""" 546 | 547 | # The following check is performed as a string comparison 548 | # so that ipy_table does not need to require (import) numpy. 549 | if str(type(data)) == "": 550 | return data.tolist() 551 | 552 | return data 553 | 554 | 555 | def _key_is_valid(dictionary, key): 556 | """Test that a dictionary key exists and that it's value is not blank.""" 557 | if key in dictionary: 558 | if dictionary[key]: 559 | return True 560 | return False 561 | -------------------------------------------------------------------------------- /ipy_table/six.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Benjamin Peterson 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | """Utilities for writing code that runs on Python 2 and 3""" 22 | 23 | from __future__ import absolute_import 24 | 25 | import functools 26 | import itertools 27 | import operator 28 | import sys 29 | import types 30 | 31 | __author__ = "Benjamin Peterson " 32 | __version__ = "1.10.0" 33 | 34 | 35 | # Useful for very coarse version differentiation. 36 | PY2 = sys.version_info[0] == 2 37 | PY3 = sys.version_info[0] == 3 38 | PY34 = sys.version_info[0:2] >= (3, 4) 39 | 40 | if PY3: 41 | string_types = str, 42 | integer_types = int, 43 | class_types = type, 44 | text_type = str 45 | binary_type = bytes 46 | 47 | MAXSIZE = sys.maxsize 48 | else: 49 | string_types = basestring, 50 | integer_types = (int, long) 51 | class_types = (type, types.ClassType) 52 | text_type = unicode 53 | binary_type = str 54 | 55 | if sys.platform.startswith("java"): 56 | # Jython always uses 32 bits. 57 | MAXSIZE = int((1 << 31) - 1) 58 | else: 59 | # It's possible to have sizeof(long) != sizeof(Py_ssize_t). 60 | class X(object): 61 | 62 | def __len__(self): 63 | return 1 << 31 64 | try: 65 | len(X()) 66 | except OverflowError: 67 | # 32-bit 68 | MAXSIZE = int((1 << 31) - 1) 69 | else: 70 | # 64-bit 71 | MAXSIZE = int((1 << 63) - 1) 72 | del X 73 | 74 | 75 | def _add_doc(func, doc): 76 | """Add documentation to a function.""" 77 | func.__doc__ = doc 78 | 79 | 80 | def _import_module(name): 81 | """Import module, returning the module after the last dot.""" 82 | __import__(name) 83 | return sys.modules[name] 84 | 85 | 86 | class _LazyDescr(object): 87 | 88 | def __init__(self, name): 89 | self.name = name 90 | 91 | def __get__(self, obj, tp): 92 | result = self._resolve() 93 | setattr(obj, self.name, result) # Invokes __set__. 94 | try: 95 | # This is a bit ugly, but it avoids running this again by 96 | # removing this descriptor. 97 | delattr(obj.__class__, self.name) 98 | except AttributeError: 99 | pass 100 | return result 101 | 102 | 103 | class MovedModule(_LazyDescr): 104 | 105 | def __init__(self, name, old, new=None): 106 | super(MovedModule, self).__init__(name) 107 | if PY3: 108 | if new is None: 109 | new = name 110 | self.mod = new 111 | else: 112 | self.mod = old 113 | 114 | def _resolve(self): 115 | return _import_module(self.mod) 116 | 117 | def __getattr__(self, attr): 118 | _module = self._resolve() 119 | value = getattr(_module, attr) 120 | setattr(self, attr, value) 121 | return value 122 | 123 | 124 | class _LazyModule(types.ModuleType): 125 | 126 | def __init__(self, name): 127 | super(_LazyModule, self).__init__(name) 128 | self.__doc__ = self.__class__.__doc__ 129 | 130 | def __dir__(self): 131 | attrs = ["__doc__", "__name__"] 132 | attrs += [attr.name for attr in self._moved_attributes] 133 | return attrs 134 | 135 | # Subclasses should override this 136 | _moved_attributes = [] 137 | 138 | 139 | class MovedAttribute(_LazyDescr): 140 | 141 | def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): 142 | super(MovedAttribute, self).__init__(name) 143 | if PY3: 144 | if new_mod is None: 145 | new_mod = name 146 | self.mod = new_mod 147 | if new_attr is None: 148 | if old_attr is None: 149 | new_attr = name 150 | else: 151 | new_attr = old_attr 152 | self.attr = new_attr 153 | else: 154 | self.mod = old_mod 155 | if old_attr is None: 156 | old_attr = name 157 | self.attr = old_attr 158 | 159 | def _resolve(self): 160 | module = _import_module(self.mod) 161 | return getattr(module, self.attr) 162 | 163 | 164 | class _SixMetaPathImporter(object): 165 | 166 | """ 167 | A meta path importer to import six.moves and its submodules. 168 | 169 | This class implements a PEP302 finder and loader. It should be compatible 170 | with Python 2.5 and all existing versions of Python3 171 | """ 172 | 173 | def __init__(self, six_module_name): 174 | self.name = six_module_name 175 | self.known_modules = {} 176 | 177 | def _add_module(self, mod, *fullnames): 178 | for fullname in fullnames: 179 | self.known_modules[self.name + "." + fullname] = mod 180 | 181 | def _get_module(self, fullname): 182 | return self.known_modules[self.name + "." + fullname] 183 | 184 | def find_module(self, fullname, path=None): 185 | if fullname in self.known_modules: 186 | return self 187 | return None 188 | 189 | def __get_module(self, fullname): 190 | try: 191 | return self.known_modules[fullname] 192 | except KeyError: 193 | raise ImportError("This loader does not know module " + fullname) 194 | 195 | def load_module(self, fullname): 196 | try: 197 | # in case of a reload 198 | return sys.modules[fullname] 199 | except KeyError: 200 | pass 201 | mod = self.__get_module(fullname) 202 | if isinstance(mod, MovedModule): 203 | mod = mod._resolve() 204 | else: 205 | mod.__loader__ = self 206 | sys.modules[fullname] = mod 207 | return mod 208 | 209 | def is_package(self, fullname): 210 | """ 211 | Return true, if the named module is a package. 212 | 213 | We need this method to get correct spec objects with 214 | Python 3.4 (see PEP451) 215 | """ 216 | return hasattr(self.__get_module(fullname), "__path__") 217 | 218 | def get_code(self, fullname): 219 | """Return None 220 | 221 | Required, if is_package is implemented""" 222 | self.__get_module(fullname) # eventually raises ImportError 223 | return None 224 | get_source = get_code # same as get_code 225 | 226 | _importer = _SixMetaPathImporter(__name__) 227 | 228 | 229 | class _MovedItems(_LazyModule): 230 | 231 | """Lazy loading of moved objects""" 232 | __path__ = [] # mark as package 233 | 234 | 235 | _moved_attributes = [ 236 | MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), 237 | MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), 238 | MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), 239 | MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), 240 | MovedAttribute("intern", "__builtin__", "sys"), 241 | MovedAttribute("map", "itertools", "builtins", "imap", "map"), 242 | MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), 243 | MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), 244 | MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), 245 | MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), 246 | MovedAttribute("reduce", "__builtin__", "functools"), 247 | MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), 248 | MovedAttribute("StringIO", "StringIO", "io"), 249 | MovedAttribute("UserDict", "UserDict", "collections"), 250 | MovedAttribute("UserList", "UserList", "collections"), 251 | MovedAttribute("UserString", "UserString", "collections"), 252 | MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), 253 | MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), 254 | MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), 255 | MovedModule("builtins", "__builtin__"), 256 | MovedModule("configparser", "ConfigParser"), 257 | MovedModule("copyreg", "copy_reg"), 258 | MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), 259 | MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), 260 | MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), 261 | MovedModule("http_cookies", "Cookie", "http.cookies"), 262 | MovedModule("html_entities", "htmlentitydefs", "html.entities"), 263 | MovedModule("html_parser", "HTMLParser", "html.parser"), 264 | MovedModule("http_client", "httplib", "http.client"), 265 | MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), 266 | MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), 267 | MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), 268 | MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), 269 | MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), 270 | MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), 271 | MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), 272 | MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), 273 | MovedModule("cPickle", "cPickle", "pickle"), 274 | MovedModule("queue", "Queue"), 275 | MovedModule("reprlib", "repr"), 276 | MovedModule("socketserver", "SocketServer"), 277 | MovedModule("_thread", "thread", "_thread"), 278 | MovedModule("tkinter", "Tkinter"), 279 | MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), 280 | MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), 281 | MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), 282 | MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), 283 | MovedModule("tkinter_tix", "Tix", "tkinter.tix"), 284 | MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), 285 | MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), 286 | MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), 287 | MovedModule("tkinter_colorchooser", "tkColorChooser", 288 | "tkinter.colorchooser"), 289 | MovedModule("tkinter_commondialog", "tkCommonDialog", 290 | "tkinter.commondialog"), 291 | MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), 292 | MovedModule("tkinter_font", "tkFont", "tkinter.font"), 293 | MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), 294 | MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", 295 | "tkinter.simpledialog"), 296 | MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), 297 | MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), 298 | MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), 299 | MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), 300 | MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), 301 | MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), 302 | ] 303 | # Add windows specific modules. 304 | if sys.platform == "win32": 305 | _moved_attributes += [ 306 | MovedModule("winreg", "_winreg"), 307 | ] 308 | 309 | for attr in _moved_attributes: 310 | setattr(_MovedItems, attr.name, attr) 311 | if isinstance(attr, MovedModule): 312 | _importer._add_module(attr, "moves." + attr.name) 313 | del attr 314 | 315 | _MovedItems._moved_attributes = _moved_attributes 316 | 317 | moves = _MovedItems(__name__ + ".moves") 318 | _importer._add_module(moves, "moves") 319 | 320 | 321 | class Module_six_moves_urllib_parse(_LazyModule): 322 | 323 | """Lazy loading of moved objects in six.moves.urllib_parse""" 324 | 325 | 326 | _urllib_parse_moved_attributes = [ 327 | MovedAttribute("ParseResult", "urlparse", "urllib.parse"), 328 | MovedAttribute("SplitResult", "urlparse", "urllib.parse"), 329 | MovedAttribute("parse_qs", "urlparse", "urllib.parse"), 330 | MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), 331 | MovedAttribute("urldefrag", "urlparse", "urllib.parse"), 332 | MovedAttribute("urljoin", "urlparse", "urllib.parse"), 333 | MovedAttribute("urlparse", "urlparse", "urllib.parse"), 334 | MovedAttribute("urlsplit", "urlparse", "urllib.parse"), 335 | MovedAttribute("urlunparse", "urlparse", "urllib.parse"), 336 | MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), 337 | MovedAttribute("quote", "urllib", "urllib.parse"), 338 | MovedAttribute("quote_plus", "urllib", "urllib.parse"), 339 | MovedAttribute("unquote", "urllib", "urllib.parse"), 340 | MovedAttribute("unquote_plus", "urllib", "urllib.parse"), 341 | MovedAttribute("urlencode", "urllib", "urllib.parse"), 342 | MovedAttribute("splitquery", "urllib", "urllib.parse"), 343 | MovedAttribute("splittag", "urllib", "urllib.parse"), 344 | MovedAttribute("splituser", "urllib", "urllib.parse"), 345 | MovedAttribute("splitvalue", "urllib", "urllib.parse"), 346 | MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), 347 | MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), 348 | MovedAttribute("uses_params", "urlparse", "urllib.parse"), 349 | MovedAttribute("uses_query", "urlparse", "urllib.parse"), 350 | MovedAttribute("uses_relative", "urlparse", "urllib.parse"), 351 | ] 352 | for attr in _urllib_parse_moved_attributes: 353 | setattr(Module_six_moves_urllib_parse, attr.name, attr) 354 | del attr 355 | 356 | Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes 357 | 358 | _importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), 359 | "moves.urllib_parse", "moves.urllib.parse") 360 | 361 | 362 | class Module_six_moves_urllib_error(_LazyModule): 363 | 364 | """Lazy loading of moved objects in six.moves.urllib_error""" 365 | 366 | 367 | _urllib_error_moved_attributes = [ 368 | MovedAttribute("URLError", "urllib2", "urllib.error"), 369 | MovedAttribute("HTTPError", "urllib2", "urllib.error"), 370 | MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), 371 | ] 372 | for attr in _urllib_error_moved_attributes: 373 | setattr(Module_six_moves_urllib_error, attr.name, attr) 374 | del attr 375 | 376 | Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes 377 | 378 | _importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), 379 | "moves.urllib_error", "moves.urllib.error") 380 | 381 | 382 | class Module_six_moves_urllib_request(_LazyModule): 383 | 384 | """Lazy loading of moved objects in six.moves.urllib_request""" 385 | 386 | 387 | _urllib_request_moved_attributes = [ 388 | MovedAttribute("urlopen", "urllib2", "urllib.request"), 389 | MovedAttribute("install_opener", "urllib2", "urllib.request"), 390 | MovedAttribute("build_opener", "urllib2", "urllib.request"), 391 | MovedAttribute("pathname2url", "urllib", "urllib.request"), 392 | MovedAttribute("url2pathname", "urllib", "urllib.request"), 393 | MovedAttribute("getproxies", "urllib", "urllib.request"), 394 | MovedAttribute("Request", "urllib2", "urllib.request"), 395 | MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), 396 | MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), 397 | MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), 398 | MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), 399 | MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), 400 | MovedAttribute("BaseHandler", "urllib2", "urllib.request"), 401 | MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), 402 | MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), 403 | MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), 404 | MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), 405 | MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), 406 | MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), 407 | MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), 408 | MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), 409 | MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), 410 | MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), 411 | MovedAttribute("FileHandler", "urllib2", "urllib.request"), 412 | MovedAttribute("FTPHandler", "urllib2", "urllib.request"), 413 | MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), 414 | MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), 415 | MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), 416 | MovedAttribute("urlretrieve", "urllib", "urllib.request"), 417 | MovedAttribute("urlcleanup", "urllib", "urllib.request"), 418 | MovedAttribute("URLopener", "urllib", "urllib.request"), 419 | MovedAttribute("FancyURLopener", "urllib", "urllib.request"), 420 | MovedAttribute("proxy_bypass", "urllib", "urllib.request"), 421 | ] 422 | for attr in _urllib_request_moved_attributes: 423 | setattr(Module_six_moves_urllib_request, attr.name, attr) 424 | del attr 425 | 426 | Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes 427 | 428 | _importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), 429 | "moves.urllib_request", "moves.urllib.request") 430 | 431 | 432 | class Module_six_moves_urllib_response(_LazyModule): 433 | 434 | """Lazy loading of moved objects in six.moves.urllib_response""" 435 | 436 | 437 | _urllib_response_moved_attributes = [ 438 | MovedAttribute("addbase", "urllib", "urllib.response"), 439 | MovedAttribute("addclosehook", "urllib", "urllib.response"), 440 | MovedAttribute("addinfo", "urllib", "urllib.response"), 441 | MovedAttribute("addinfourl", "urllib", "urllib.response"), 442 | ] 443 | for attr in _urllib_response_moved_attributes: 444 | setattr(Module_six_moves_urllib_response, attr.name, attr) 445 | del attr 446 | 447 | Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes 448 | 449 | _importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), 450 | "moves.urllib_response", "moves.urllib.response") 451 | 452 | 453 | class Module_six_moves_urllib_robotparser(_LazyModule): 454 | 455 | """Lazy loading of moved objects in six.moves.urllib_robotparser""" 456 | 457 | 458 | _urllib_robotparser_moved_attributes = [ 459 | MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), 460 | ] 461 | for attr in _urllib_robotparser_moved_attributes: 462 | setattr(Module_six_moves_urllib_robotparser, attr.name, attr) 463 | del attr 464 | 465 | Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes 466 | 467 | _importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), 468 | "moves.urllib_robotparser", "moves.urllib.robotparser") 469 | 470 | 471 | class Module_six_moves_urllib(types.ModuleType): 472 | 473 | """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" 474 | __path__ = [] # mark as package 475 | parse = _importer._get_module("moves.urllib_parse") 476 | error = _importer._get_module("moves.urllib_error") 477 | request = _importer._get_module("moves.urllib_request") 478 | response = _importer._get_module("moves.urllib_response") 479 | robotparser = _importer._get_module("moves.urllib_robotparser") 480 | 481 | def __dir__(self): 482 | return ['parse', 'error', 'request', 'response', 'robotparser'] 483 | 484 | _importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), 485 | "moves.urllib") 486 | 487 | 488 | def add_move(move): 489 | """Add an item to six.moves.""" 490 | setattr(_MovedItems, move.name, move) 491 | 492 | 493 | def remove_move(name): 494 | """Remove item from six.moves.""" 495 | try: 496 | delattr(_MovedItems, name) 497 | except AttributeError: 498 | try: 499 | del moves.__dict__[name] 500 | except KeyError: 501 | raise AttributeError("no such move, %r" % (name,)) 502 | 503 | 504 | if PY3: 505 | _meth_func = "__func__" 506 | _meth_self = "__self__" 507 | 508 | _func_closure = "__closure__" 509 | _func_code = "__code__" 510 | _func_defaults = "__defaults__" 511 | _func_globals = "__globals__" 512 | else: 513 | _meth_func = "im_func" 514 | _meth_self = "im_self" 515 | 516 | _func_closure = "func_closure" 517 | _func_code = "func_code" 518 | _func_defaults = "func_defaults" 519 | _func_globals = "func_globals" 520 | 521 | 522 | try: 523 | advance_iterator = next 524 | except NameError: 525 | def advance_iterator(it): 526 | return it.next() 527 | next = advance_iterator 528 | 529 | 530 | try: 531 | callable = callable 532 | except NameError: 533 | def callable(obj): 534 | return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) 535 | 536 | 537 | if PY3: 538 | def get_unbound_function(unbound): 539 | return unbound 540 | 541 | create_bound_method = types.MethodType 542 | 543 | def create_unbound_method(func, cls): 544 | return func 545 | 546 | Iterator = object 547 | else: 548 | def get_unbound_function(unbound): 549 | return unbound.im_func 550 | 551 | def create_bound_method(func, obj): 552 | return types.MethodType(func, obj, obj.__class__) 553 | 554 | def create_unbound_method(func, cls): 555 | return types.MethodType(func, None, cls) 556 | 557 | class Iterator(object): 558 | 559 | def next(self): 560 | return type(self).__next__(self) 561 | 562 | callable = callable 563 | _add_doc(get_unbound_function, 564 | """Get the function out of a possibly unbound function""") 565 | 566 | 567 | get_method_function = operator.attrgetter(_meth_func) 568 | get_method_self = operator.attrgetter(_meth_self) 569 | get_function_closure = operator.attrgetter(_func_closure) 570 | get_function_code = operator.attrgetter(_func_code) 571 | get_function_defaults = operator.attrgetter(_func_defaults) 572 | get_function_globals = operator.attrgetter(_func_globals) 573 | 574 | 575 | if PY3: 576 | def iterkeys(d, **kw): 577 | return iter(d.keys(**kw)) 578 | 579 | def itervalues(d, **kw): 580 | return iter(d.values(**kw)) 581 | 582 | def iteritems(d, **kw): 583 | return iter(d.items(**kw)) 584 | 585 | def iterlists(d, **kw): 586 | return iter(d.lists(**kw)) 587 | 588 | viewkeys = operator.methodcaller("keys") 589 | 590 | viewvalues = operator.methodcaller("values") 591 | 592 | viewitems = operator.methodcaller("items") 593 | else: 594 | def iterkeys(d, **kw): 595 | return d.iterkeys(**kw) 596 | 597 | def itervalues(d, **kw): 598 | return d.itervalues(**kw) 599 | 600 | def iteritems(d, **kw): 601 | return d.iteritems(**kw) 602 | 603 | def iterlists(d, **kw): 604 | return d.iterlists(**kw) 605 | 606 | viewkeys = operator.methodcaller("viewkeys") 607 | 608 | viewvalues = operator.methodcaller("viewvalues") 609 | 610 | viewitems = operator.methodcaller("viewitems") 611 | 612 | _add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") 613 | _add_doc(itervalues, "Return an iterator over the values of a dictionary.") 614 | _add_doc(iteritems, 615 | "Return an iterator over the (key, value) pairs of a dictionary.") 616 | _add_doc(iterlists, 617 | "Return an iterator over the (key, [values]) pairs of a dictionary.") 618 | 619 | 620 | if PY3: 621 | def b(s): 622 | return s.encode("latin-1") 623 | 624 | def u(s): 625 | return s 626 | unichr = chr 627 | import struct 628 | int2byte = struct.Struct(">B").pack 629 | del struct 630 | byte2int = operator.itemgetter(0) 631 | indexbytes = operator.getitem 632 | iterbytes = iter 633 | import io 634 | StringIO = io.StringIO 635 | BytesIO = io.BytesIO 636 | _assertCountEqual = "assertCountEqual" 637 | if sys.version_info[1] <= 1: 638 | _assertRaisesRegex = "assertRaisesRegexp" 639 | _assertRegex = "assertRegexpMatches" 640 | else: 641 | _assertRaisesRegex = "assertRaisesRegex" 642 | _assertRegex = "assertRegex" 643 | else: 644 | def b(s): 645 | return s 646 | # Workaround for standalone backslash 647 | 648 | def u(s): 649 | return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") 650 | unichr = unichr 651 | int2byte = chr 652 | 653 | def byte2int(bs): 654 | return ord(bs[0]) 655 | 656 | def indexbytes(buf, i): 657 | return ord(buf[i]) 658 | iterbytes = functools.partial(itertools.imap, ord) 659 | import StringIO 660 | StringIO = BytesIO = StringIO.StringIO 661 | _assertCountEqual = "assertItemsEqual" 662 | _assertRaisesRegex = "assertRaisesRegexp" 663 | _assertRegex = "assertRegexpMatches" 664 | _add_doc(b, """Byte literal""") 665 | _add_doc(u, """Text literal""") 666 | 667 | 668 | def assertCountEqual(self, *args, **kwargs): 669 | return getattr(self, _assertCountEqual)(*args, **kwargs) 670 | 671 | 672 | def assertRaisesRegex(self, *args, **kwargs): 673 | return getattr(self, _assertRaisesRegex)(*args, **kwargs) 674 | 675 | 676 | def assertRegex(self, *args, **kwargs): 677 | return getattr(self, _assertRegex)(*args, **kwargs) 678 | 679 | 680 | if PY3: 681 | exec_ = getattr(moves.builtins, "exec") 682 | 683 | def reraise(tp, value, tb=None): 684 | try: 685 | if value is None: 686 | value = tp() 687 | if value.__traceback__ is not tb: 688 | raise value.with_traceback(tb) 689 | raise value 690 | finally: 691 | value = None 692 | tb = None 693 | 694 | else: 695 | def exec_(_code_, _globs_=None, _locs_=None): 696 | """Execute code in a namespace.""" 697 | if _globs_ is None: 698 | frame = sys._getframe(1) 699 | _globs_ = frame.f_globals 700 | if _locs_ is None: 701 | _locs_ = frame.f_locals 702 | del frame 703 | elif _locs_ is None: 704 | _locs_ = _globs_ 705 | exec("""exec _code_ in _globs_, _locs_""") 706 | 707 | exec_("""def reraise(tp, value, tb=None): 708 | try: 709 | raise tp, value, tb 710 | finally: 711 | tb = None 712 | """) 713 | 714 | 715 | if sys.version_info[:2] == (3, 2): 716 | exec_("""def raise_from(value, from_value): 717 | try: 718 | if from_value is None: 719 | raise value 720 | raise value from from_value 721 | finally: 722 | value = None 723 | """) 724 | elif sys.version_info[:2] > (3, 2): 725 | exec_("""def raise_from(value, from_value): 726 | try: 727 | raise value from from_value 728 | finally: 729 | value = None 730 | """) 731 | else: 732 | def raise_from(value, from_value): 733 | raise value 734 | 735 | 736 | print_ = getattr(moves.builtins, "print", None) 737 | if print_ is None: 738 | def print_(*args, **kwargs): 739 | """The new-style print function for Python 2.4 and 2.5.""" 740 | fp = kwargs.pop("file", sys.stdout) 741 | if fp is None: 742 | return 743 | 744 | def write(data): 745 | if not isinstance(data, basestring): 746 | data = str(data) 747 | # If the file has an encoding, encode unicode with it. 748 | if (isinstance(fp, file) and 749 | isinstance(data, unicode) and 750 | fp.encoding is not None): 751 | errors = getattr(fp, "errors", None) 752 | if errors is None: 753 | errors = "strict" 754 | data = data.encode(fp.encoding, errors) 755 | fp.write(data) 756 | want_unicode = False 757 | sep = kwargs.pop("sep", None) 758 | if sep is not None: 759 | if isinstance(sep, unicode): 760 | want_unicode = True 761 | elif not isinstance(sep, str): 762 | raise TypeError("sep must be None or a string") 763 | end = kwargs.pop("end", None) 764 | if end is not None: 765 | if isinstance(end, unicode): 766 | want_unicode = True 767 | elif not isinstance(end, str): 768 | raise TypeError("end must be None or a string") 769 | if kwargs: 770 | raise TypeError("invalid keyword arguments to print()") 771 | if not want_unicode: 772 | for arg in args: 773 | if isinstance(arg, unicode): 774 | want_unicode = True 775 | break 776 | if want_unicode: 777 | newline = unicode("\n") 778 | space = unicode(" ") 779 | else: 780 | newline = "\n" 781 | space = " " 782 | if sep is None: 783 | sep = space 784 | if end is None: 785 | end = newline 786 | for i, arg in enumerate(args): 787 | if i: 788 | write(sep) 789 | write(arg) 790 | write(end) 791 | if sys.version_info[:2] < (3, 3): 792 | _print = print_ 793 | 794 | def print_(*args, **kwargs): 795 | fp = kwargs.get("file", sys.stdout) 796 | flush = kwargs.pop("flush", False) 797 | _print(*args, **kwargs) 798 | if flush and fp is not None: 799 | fp.flush() 800 | 801 | _add_doc(reraise, """Reraise an exception.""") 802 | 803 | if sys.version_info[0:2] < (3, 4): 804 | def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, 805 | updated=functools.WRAPPER_UPDATES): 806 | def wrapper(f): 807 | f = functools.wraps(wrapped, assigned, updated)(f) 808 | f.__wrapped__ = wrapped 809 | return f 810 | return wrapper 811 | else: 812 | wraps = functools.wraps 813 | 814 | 815 | def with_metaclass(meta, *bases): 816 | """Create a base class with a metaclass.""" 817 | # This requires a bit of explanation: the basic idea is to make a dummy 818 | # metaclass for one level of class instantiation that replaces itself with 819 | # the actual metaclass. 820 | class metaclass(meta): 821 | 822 | def __new__(cls, name, this_bases, d): 823 | return meta(name, bases, d) 824 | return type.__new__(metaclass, 'temporary_class', (), {}) 825 | 826 | 827 | def add_metaclass(metaclass): 828 | """Class decorator for creating a class with a metaclass.""" 829 | def wrapper(cls): 830 | orig_vars = cls.__dict__.copy() 831 | slots = orig_vars.get('__slots__') 832 | if slots is not None: 833 | if isinstance(slots, str): 834 | slots = [slots] 835 | for slots_var in slots: 836 | orig_vars.pop(slots_var) 837 | orig_vars.pop('__dict__', None) 838 | orig_vars.pop('__weakref__', None) 839 | return metaclass(cls.__name__, cls.__bases__, orig_vars) 840 | return wrapper 841 | 842 | 843 | def python_2_unicode_compatible(klass): 844 | """ 845 | A decorator that defines __unicode__ and __str__ methods under Python 2. 846 | Under Python 3 it does nothing. 847 | 848 | To support Python 2 and 3 with a single code base, define a __str__ method 849 | returning text and apply this decorator to the class. 850 | """ 851 | if PY2: 852 | if '__str__' not in klass.__dict__: 853 | raise ValueError("@python_2_unicode_compatible cannot be applied " 854 | "to %s because it doesn't define __str__()." % 855 | klass.__name__) 856 | klass.__unicode__ = klass.__str__ 857 | klass.__str__ = lambda self: self.__unicode__().encode('utf-8') 858 | return klass 859 | 860 | 861 | # Complete the moves implementation. 862 | # This code is at the end of this module to speed up module loading. 863 | # Turn this module into a package. 864 | __path__ = [] # required for PEP 302 and PEP 451 865 | __package__ = __name__ # see PEP 366 @ReservedAssignment 866 | if globals().get("__spec__") is not None: 867 | __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable 868 | # Remove other six meta path importers, since they cause problems. This can 869 | # happen if six is removed from sys.modules and then reloaded. (Setuptools does 870 | # this for some reason.) 871 | if sys.meta_path: 872 | for i, importer in enumerate(sys.meta_path): 873 | # Here's some real nastiness: Another "instance" of the six module might 874 | # be floating around. Therefore, we can't use isinstance() to check for 875 | # the six meta path importer, since the other six instance will have 876 | # inserted an importer with different class. 877 | if (type(importer).__name__ == "_SixMetaPathImporter" and 878 | importer.name == __name__): 879 | del sys.meta_path[i] 880 | break 881 | del i, importer 882 | # Finally, add the importer to the meta path import hook. 883 | sys.meta_path.append(_importer) -------------------------------------------------------------------------------- /ipy_table/vector_manager.py: -------------------------------------------------------------------------------- 1 | ''' vectors.py 2 | 3 | A test vector manager for validating ipy_table 4 | ''' 5 | 6 | import json 7 | import re 8 | import pprint 9 | from copy import deepcopy 10 | 11 | from six import string_types 12 | import numpy as np 13 | from ipy_table import make_table, tabulate 14 | from IPython.display import display, HTML 15 | 16 | pp = pprint.PrettyPrinter(indent=4) 17 | 18 | class VectorManager(object): 19 | ''' Test vector manager for validating ipy_table 20 | ''' 21 | 22 | def __init__(self, filename=None): 23 | self.vectors = [] 24 | if filename is not None: 25 | self._load(filename) 26 | 27 | def add(self, description, data, operations, tabulate_columns=None): 28 | ''' Add a new vector 29 | 30 | Creates a test vector describing the various data and method calls 31 | necessary to create a table object. 32 | 33 | Can be used to create vectors describing tables constructed using 34 | make_table() or tabulate() 35 | 36 | The appropriate table constructors and methods will be called, 37 | then the resulting table object will be queried to get its 38 | HTML representation, which will be set in the vector's 39 | vector['expected_HTML'] field. 40 | 41 | At a later time, the vector can be checked using run_vector() 42 | 43 | Arguments: 44 | description: A text description for the vector 45 | If there is one and only one operation in the operations 46 | argument, then description can be blank, and a default 47 | description will be created from the operation. 48 | data: The data from which to create the table. 49 | For make_table() vectors this is a list of lists. 50 | For tabulate() vectors this is a list 51 | operations: A list of tuples of the form (method, kwargs) 52 | were kwargs is a dict. 53 | tabulate_columns: If None then the vector is a make_table() 54 | vector, else it is a tabulate() vector and this value 55 | represents the number of columns. 56 | ''' 57 | if not description: 58 | if len(operations) != 1: 59 | raise ValueError ( 60 | 'Must pass one and only one operation to use default description') 61 | else: 62 | # Create default description from requested operation 63 | method_name, kwargs_dict = operations[0] 64 | description = self.render_table_call( 65 | method_name, kwargs_dict) 66 | 67 | vector = dict( 68 | description=description, 69 | data=data, 70 | operations=operations, 71 | expected_html='', 72 | result_html='', 73 | tabulate_columns=tabulate_columns 74 | ) 75 | 76 | # Execute the test vector 77 | self.run_vector(vector) 78 | 79 | # Since we are creating the test vector, 80 | # copy the result into expected, and clear 81 | # the result. 82 | vector['expected_html'] = vector['result_html'] 83 | vector['result_html'] = '' 84 | 85 | self.vectors.append(vector) 86 | print('Vector {}:'.format(len(self.vectors)-1)) 87 | self._show(vector, indent = ' ') 88 | 89 | def show_all(self): 90 | ''' Show all test vectors 91 | 92 | This method should be run in a Jupyter notebook 93 | environment, as the results use display() to 94 | render HTML results. 95 | ''' 96 | for index, vector in enumerate(self.vectors): 97 | print('Vector {}:'.format(index)) 98 | self._show(vector, indent=' ') 99 | 100 | def save(self, filename): 101 | ''' Save test vectors to a JSON file 102 | ''' 103 | 104 | # Convert numpy types for (custom) JSON serialization 105 | save_vectors = deepcopy(self.vectors) 106 | for vector in save_vectors: 107 | vector['data'] = _serialize_numpy(vector['data']) 108 | 109 | with open(filename, 'w') as out_file: 110 | out_file.write(json.dumps(save_vectors, indent=4)) 111 | print('Saved {} vectors.'.format(len(save_vectors))) 112 | 113 | def _load(self, filename): 114 | ''' Load test vectors from a JSON file 115 | ''' 116 | with open(filename, 'r') as in_file: 117 | self.vectors = json.load(in_file) 118 | 119 | # Deserialize numpy types from (custom) JSON storage format 120 | for vector in self.vectors: 121 | vector['data'] = _deserialize_numpy(vector['data']) 122 | 123 | @staticmethod 124 | def _show(vector, indent=''): 125 | ''' Show a test vector 126 | 127 | This method should be run in a Jupyter notebook 128 | environment, as it uses display() to 129 | render expected/result HTML. 130 | ''' 131 | print(indent + 'description: ' + repr(vector['description'])) 132 | print(indent + 'data:{}'.format(pp.pformat(vector['data']))) 133 | print(indent + 'operations:') 134 | if not vector['operations']: 135 | print(indent + ' (None)') 136 | else: 137 | for operation in vector['operations']: 138 | method_name, kwargs_dict = operation 139 | print(indent + 140 | ' ' + 141 | 'table.' + 142 | method_name + 143 | '(' + 144 | _kwargs_to_str(kwargs_dict) + 145 | ')' 146 | ) 147 | print(indent + 'expected_html:') 148 | display(HTML(_html_indent(vector['expected_html']))) 149 | if vector['result_html']: 150 | print(indent + 'result_html:') 151 | display(HTML(_html_indent(vector['result_html']))) 152 | 153 | @staticmethod 154 | def render_table_call(method_name, kwargs_dict): 155 | ''' Renders a method name and a kwargs dict into a table call 156 | 157 | render_table_call('my_method', {'this': 5, 'that':7}) 158 | => 'table.my_method(this=5, that=7)' 159 | ''' 160 | return ( 161 | 'table.' + 162 | method_name + 163 | '(' + 164 | _kwargs_to_str(kwargs_dict) + 165 | ')') 166 | 167 | @staticmethod 168 | def run_vector(vector): 169 | ''' Executes a test vector, sets vector['result_html'] to the result 170 | 171 | Returns: True if (after execution) vector['result_html'] matches 172 | vector['expected_html'] 173 | ''' 174 | if vector['tabulate_columns']: 175 | # This is a tabulate() vector. 176 | table = tabulate( 177 | vector['data'], 178 | vector['tabulate_columns']) 179 | else: 180 | # This is a make_table() vector 181 | table = make_table(vector['data']) 182 | 183 | # For each operation, call the designated table method with 184 | # the designated keyword arguments 185 | for operation in vector['operations']: 186 | method_name, kwargs_dict = operation 187 | method = getattr(table, method_name) 188 | method(**kwargs_dict) 189 | 190 | # Update the vector with the result 191 | vector['result_html'] = table._repr_html_() 192 | 193 | return vector['result_html'] == vector['expected_html'] 194 | 195 | def _kwargs_to_str(kwargs_dict): 196 | ''' Converts a kwargs dict into a string representation of normal fn call syntax 197 | 198 | render_table_call({'this': 5, 'that':7}) => 'this=5, that=7' 199 | ''' 200 | return ', '.join([key + '=' + repr(value) for key, value in kwargs_dict.items()]) 201 | 202 | def _html_indent(html): 203 | '''Return html wrapped by an indenting div''' 204 | return '
' + html + '
' 205 | 206 | def _serialize_numpy(item): 207 | '''Serialize a list or item containing 0 or more numpy float objects 208 | 209 | Arguments: 210 | item: list or object 211 | Returns: 212 | The item, with all instances of numpy float types converted to a 213 | string of the form: 'numpy.()' 214 | 215 | Example: 216 | _serialize_numpy(numpy.float64(1.234)) => 'numpy.float64(1.234)' 217 | _serialize_numpy([ numpy.float64(1.234), numpy.float32(5.678)]) => 218 | ['numpy.float64(1.234)', 'numpy.float32(5.678)'] 219 | ''' 220 | if isinstance(item, (list, tuple)): 221 | # item is a list. Process the elements 222 | return [_serialize_numpy(x) for x in item] 223 | else: 224 | type_str = repr(type(item)) 225 | if type_str.startswith("").strip("()' 229 | # example: 230 | # 'numpy.float64(1.234)' 231 | return type_str + '(' + str(item) + ')' 232 | 233 | # Return the item unmodified 234 | return item 235 | 236 | def _deserialize_numpy(item): 237 | '''De-serialize a list or item containing 0 or more serialized numpy float objects 238 | 239 | Serialized numpy objects have the form: 'numpy.()' 240 | 241 | Arguments: 242 | item: list or object 243 | Returns: 244 | The item, with all instances of serialized numpy float types converted 245 | to the associated numpy float type. 246 | 247 | Example: 248 | _serialize_numpy('numpy.float64(1.234)') => numpy.float64(1.234) 249 | _serialize_numpy(['numpy.float64(1.234)', 'numpy.float32(5.678)']) => 250 | [ numpy.float64(1.234), numpy.float32(5.678)] 251 | ''' 252 | 253 | if isinstance(item, (list, tuple)): 254 | # item is a list. Process the elements 255 | return [_deserialize_numpy(x) for x in item] 256 | else: 257 | if isinstance(item, string_types) and item.startswith('numpy.'): 258 | match = re.match(r'^numpy\.(\w*)\(([\d\.]*)\)', item) 259 | if match and len(match.groups()) == 2: 260 | type_str, value_str = match.groups() 261 | if type_str == 'float16': 262 | return np.float16(value_str) 263 | elif type_str == 'float32': 264 | return np.float32(value_str) 265 | elif type_str == 'float64': 266 | return np.float64(value_str) 267 | elif type_str == 'float128': 268 | return np.float128(value_str) 269 | 270 | raise ValueError("Unexpected numpy serialization format: '{}'".format(item)) 271 | 272 | # Return the item unmodified 273 | return item 274 | 275 | 276 | -------------------------------------------------------------------------------- /ipy_table/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.15.1' -------------------------------------------------------------------------------- /notebooks/add_parent_to_path.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | # Add parent directory so we can import ipy_table. 5 | # Add it at the beginning of sys.path so that the local version is used rather than any installed version. 6 | module_path = os.path.abspath(os.path.join('..')) 7 | if module_path not in sys.path: 8 | sys.path.insert(0, module_path) -------------------------------------------------------------------------------- /notebooks/ipy_table-Introduction.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Introduction\n", 8 | "ipy_table is a supporting module for Jupyter Notebooks which makes it easy to create richly formatted data tables.\n", 9 | "\n", 10 | "The home page for ipy_table is at [epmoyer.github.com/ipy_table/](http://epmoyer.github.com/ipy_table/)\n", 11 | "\n", 12 | "ipy_table is maintained at [github.com/epmoyer/ipy_table](https://github.com/epmoyer/ipy_table)" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 1, 18 | "metadata": { 19 | "collapsed": true 20 | }, 21 | "outputs": [], 22 | "source": [ 23 | "import add_parent_to_path" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "# Example" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "To create a table in interactive mode, import ipy_table and call ``make_table()`` on an array.\n", 38 | "\n", 39 | "Notes:\n", 40 | "\n", 41 | "* ipy_table can accept either a \"native\" array (a list of equal-length lists) or a ``numpy.ndarray``. \n", 42 | "* Arrays passed to ipy_table typically contain integers, floats or strings, but in general they can contain other object types and ipy_table will render the result of calling ``str()`` on those objects." 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 2, 48 | "metadata": {}, 49 | "outputs": [ 50 | { 51 | "data": { 52 | "text/html": [ 53 | "
PlanetMass (kg)Diameter (km)
Mercury330219999999999996854272.00004879
Venus4896000000000000201326592.000012104
Earth5972000000000000327155712.000012735
Mars641910000000000065536000.00006772
" 54 | ], 55 | "text/plain": [ 56 | "" 57 | ] 58 | }, 59 | "execution_count": 2, 60 | "metadata": {}, 61 | "output_type": "execute_result" 62 | } 63 | ], 64 | "source": [ 65 | "from ipy_table import *\n", 66 | "planets = [\n", 67 | " ['Planet', 'Mass (kg)', 'Diameter (km)'],\n", 68 | " ['Mercury', 3.3022E23, 4879], \n", 69 | " ['Venus', 4.896E24, 12104],\n", 70 | " ['Earth', 5.972E24, 12735],\n", 71 | " ['Mars', 6.4191E23, 6772]];\n", 72 | "make_table(planets)" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": {}, 78 | "source": [ 79 | "The ``make_table()`` interface is interactive, so after calling ``make_table()`` we can call style formatting commands to modify the current table format. Here we'll apply the \"basic\" table theme.\n", 80 | "\n", 81 | "Note: Use \"basic_left\" for tables with row headers. Use \"basic_both\" for tables with row and coulmn headers." 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 3, 87 | "metadata": {}, 88 | "outputs": [ 89 | { 90 | "data": { 91 | "text/html": [ 92 | "
PlanetMass (kg)Diameter (km)
Mercury330219999999999996854272.00004879
Venus4896000000000000201326592.000012104
Earth5972000000000000327155712.000012735
Mars641910000000000065536000.00006772
" 93 | ], 94 | "text/plain": [ 95 | "" 96 | ] 97 | }, 98 | "execution_count": 3, 99 | "metadata": {}, 100 | "output_type": "execute_result" 101 | } 102 | ], 103 | "source": [ 104 | "apply_theme('basic')" 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "The Mass values are being fully expanded. By default ipy_table formats floating point numbers using the Python formatting string ``\"%0.4f\"``. We can override that by setting the ``float_format`` parameter.\n" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 4, 117 | "metadata": {}, 118 | "outputs": [ 119 | { 120 | "data": { 121 | "text/html": [ 122 | "
PlanetMass (kg)Diameter (km)
Mercury3.302E+234879
Venus4.896E+2412104
Earth5.972E+2412735
Mars6.419E+236772
" 123 | ], 124 | "text/plain": [ 125 | "" 126 | ] 127 | }, 128 | "execution_count": 4, 129 | "metadata": {}, 130 | "output_type": "execute_result" 131 | } 132 | ], 133 | "source": [ 134 | "set_global_style(float_format='%0.3E')" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [ 141 | "All cell formatting is dynamic. Custom formatting can be applied by calling ``set__style()``." 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 5, 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "data": { 151 | "text/html": [ 152 | "
PlanetMass (kg)Diameter (km)
Mercury3.302E+234879
Venus4.896E+2412104
Earth5.972E+2412735
Mars6.419E+236772
" 153 | ], 154 | "text/plain": [ 155 | "" 156 | ] 157 | }, 158 | "execution_count": 5, 159 | "metadata": {}, 160 | "output_type": "execute_result" 161 | } 162 | ], 163 | "source": [ 164 | "set_row_style(3,color='yellow')" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "For documentation on all ipy_table commands, see the ipy_table reference notebook (ipy_table-Reference.ipynb)" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": null, 177 | "metadata": { 178 | "collapsed": true 179 | }, 180 | "outputs": [], 181 | "source": [] 182 | } 183 | ], 184 | "metadata": { 185 | "kernelspec": { 186 | "display_name": "Python [default]", 187 | "language": "python", 188 | "name": "python2" 189 | }, 190 | "language_info": { 191 | "codemirror_mode": { 192 | "name": "ipython", 193 | "version": 2 194 | }, 195 | "file_extension": ".py", 196 | "mimetype": "text/x-python", 197 | "name": "python", 198 | "nbconvert_exporter": "python", 199 | "pygments_lexer": "ipython2", 200 | "version": "2.7.13" 201 | } 202 | }, 203 | "nbformat": 4, 204 | "nbformat_minor": 1 205 | } 206 | -------------------------------------------------------------------------------- /notebooks/ipy_table-Test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Test" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "This set of tests is manually executed today. These tests will be migrated to py.test and this notebook will be removed." 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": { 21 | "collapsed": true 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "import add_parent_to_path" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "### Badly formed array" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 2, 38 | "metadata": {}, 39 | "outputs": [ 40 | { 41 | "name": "stdout", 42 | "output_type": "stream", 43 | "text": [ 44 | "Pass\n" 45 | ] 46 | } 47 | ], 48 | "source": [ 49 | "from ipy_table import *\n", 50 | "array = [[0, 1, 2], [3, 4]]\n", 51 | "try:\n", 52 | " t = IpyTable(array)\n", 53 | "except ValueError:\n", 54 | " print 'Pass'\n", 55 | "else:\n", 56 | " print 'Fail'" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "### Row range checking" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 3, 69 | "metadata": {}, 70 | "outputs": [ 71 | { 72 | "name": "stdout", 73 | "output_type": "stream", 74 | "text": [ 75 | "Pass (Bad row (2). Expected row in range 0 to 1.)\n" 76 | ] 77 | } 78 | ], 79 | "source": [ 80 | "array = [[0, 1], [2, 3]]\n", 81 | "t = IpyTable(array)\n", 82 | "try:\n", 83 | " t.set_cell_style(2, 0, color='red')\n", 84 | "except ValueError, error_text:\n", 85 | " print 'Pass (%s)' % error_text\n", 86 | "else:\n", 87 | " print 'Fail'" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "### Unknown Theme" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 4, 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "name": "stdout", 104 | "output_type": "stream", 105 | "text": [ 106 | "Pass (Unknown theme \"monty\". Expected one of ['basic', 'basic_left', 'basic_both'].)\n" 107 | ] 108 | } 109 | ], 110 | "source": [ 111 | "array = [[0, 1], [2, 3]]\n", 112 | "t = IpyTable(array)\n", 113 | "try:\n", 114 | " t.apply_theme(\"monty\")\n", 115 | "except ValueError, error_text:\n", 116 | " print 'Pass (%s)' % error_text\n", 117 | "else:\n", 118 | " print 'Fail'" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "### Random Styles" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 5, 131 | "metadata": {}, 132 | "outputs": [ 133 | { 134 | "data": { 135 | "text/html": [ 136 | "
245665L1XGLVI54494750927.9140EDK5MZR9VCK342715
727540.9032982393.3803CSXKM2CC0C368767060740771.3891
703378W5I6MEB4313052.8700OGVGYK3PAAPMIN32926
46462269362.300764948366F79N8L19X38R9NKSWP
U73195878L1332384.174894ANOMO11HC605060.1725
119233.9238669088.4642DV86U0E5VU5PL165453.7186417924968145.1539
" 137 | ], 138 | "text/plain": [ 139 | "" 140 | ] 141 | }, 142 | "execution_count": 5, 143 | "metadata": {}, 144 | "output_type": "execute_result" 145 | } 146 | ], 147 | "source": [ 148 | "import random\n", 149 | "from random import randint\n", 150 | "import string \n", 151 | "import ipy_table as ipt\n", 152 | "\n", 153 | "width = randint(1,6)\n", 154 | "height = randint(1,6)\n", 155 | "array = [[None for column in range(width)] for row in range(height)]\n", 156 | "for row in range(height):\n", 157 | " for column in range(width):\n", 158 | " array[row][column] = random.choice([\n", 159 | " random.random()*1000000,\n", 160 | " ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(randint(1,14))), \n", 161 | " randint(0, 1000000)])\n", 162 | "make_table(array)\n", 163 | "for i in range(20):\n", 164 | " row = randint(0, height-1)\n", 165 | " column = randint(0, width-1)\n", 166 | " style = random.choice(['color', 'bold', 'thick_border', 'no_border'])\n", 167 | " if style == 'color':\n", 168 | " set_cell_style(row, column, color=random.choice(['red', 'blue', 'green']))\n", 169 | " elif style == 'bold':\n", 170 | " set_cell_style(row, column, bold=True)\n", 171 | " elif style == 'thick_border':\n", 172 | " set_cell_style(row, column, thick_border=random.choice(['left', 'right', 'top', 'bottom']))\n", 173 | " elif style == 'no_border':\n", 174 | " set_cell_style(row, column, no_border=random.choice(['left', 'right', 'top', 'bottom']))\n", 175 | " \n", 176 | "ipt.render()" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "### thick_border value checking" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": 6, 189 | "metadata": {}, 190 | "outputs": [ 191 | { 192 | "name": "stdout", 193 | "output_type": "stream", 194 | "text": [ 195 | "Pass (thick_border must be a string of comma separated border names (e.g. \"left,right\"))\n" 196 | ] 197 | } 198 | ], 199 | "source": [ 200 | "array = [[0, 1], [2, 3]]\n", 201 | "t = IpyTable(array)\n", 202 | "try:\n", 203 | " t.set_cell_style(0, 0, thick_border=True)\n", 204 | "except TypeError, error_text:\n", 205 | " print 'Pass (%s)' % error_text\n", 206 | "else:\n", 207 | " print 'Fail'" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": 7, 213 | "metadata": {}, 214 | "outputs": [ 215 | { 216 | "name": "stdout", 217 | "output_type": "stream", 218 | "text": [ 219 | "Pass (thick_border must be a string of comma separated border names (e.g. \"left,right\"). Valid border names: set(['top', 'right', 'bottom', 'all', 'left']))\n" 220 | ] 221 | } 222 | ], 223 | "source": [ 224 | "try:\n", 225 | " t.set_cell_style(0, 0, thick_border='left + bottom')\n", 226 | "except ValueError, error_text:\n", 227 | " print 'Pass (%s)' % error_text\n", 228 | "else:\n", 229 | " print 'Fail'" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": {}, 235 | "source": [ 236 | "### no_border value checking" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": 8, 242 | "metadata": {}, 243 | "outputs": [ 244 | { 245 | "name": "stdout", 246 | "output_type": "stream", 247 | "text": [ 248 | "Pass (no_border must be a string of comma separated border names (e.g. \"left,right\"))\n" 249 | ] 250 | } 251 | ], 252 | "source": [ 253 | "array = [[0, 1], [2, 3]]\n", 254 | "t = IpyTable(array)\n", 255 | "try:\n", 256 | " t.set_cell_style(0, 0, no_border=True)\n", 257 | "except TypeError, error_text:\n", 258 | " print 'Pass (%s)' % error_text\n", 259 | "else:\n", 260 | " print 'Fail'" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": 9, 266 | "metadata": {}, 267 | "outputs": [ 268 | { 269 | "name": "stdout", 270 | "output_type": "stream", 271 | "text": [ 272 | "Pass (no_border must be a string of comma separated border names (e.g. \"left,right\"). Valid border names: set(['top', 'right', 'bottom', 'all', 'left']))\n" 273 | ] 274 | } 275 | ], 276 | "source": [ 277 | "try:\n", 278 | " t.set_cell_style(0, 0, no_border='left + bottom')\n", 279 | "except ValueError, error_text:\n", 280 | " print 'Pass (%s)' % error_text\n", 281 | "else:\n", 282 | " print 'Fail'" 283 | ] 284 | }, 285 | { 286 | "cell_type": "markdown", 287 | "metadata": {}, 288 | "source": [ 289 | "### Unicode Support" 290 | ] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": 10, 295 | "metadata": { 296 | "collapsed": true 297 | }, 298 | "outputs": [], 299 | "source": [ 300 | "a=make_table([(u'Unicode Characters: \\xe9\\u03a0\\u03a3', 1),('test2',2)])" 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": 11, 306 | "metadata": {}, 307 | "outputs": [ 308 | { 309 | "data": { 310 | "text/plain": [ 311 | "u'
Unicode Characters: \\xe9\\u03a0\\u03a31
test22
'" 312 | ] 313 | }, 314 | "execution_count": 11, 315 | "metadata": {}, 316 | "output_type": "execute_result" 317 | } 318 | ], 319 | "source": [ 320 | "a._repr_html_()" 321 | ] 322 | }, 323 | { 324 | "cell_type": "code", 325 | "execution_count": 12, 326 | "metadata": {}, 327 | "outputs": [ 328 | { 329 | "data": { 330 | "text/html": [ 331 | "
Unicode Characters: éΠΣ1
test22
" 332 | ], 333 | "text/plain": [ 334 | "" 335 | ] 336 | }, 337 | "execution_count": 12, 338 | "metadata": {}, 339 | "output_type": "execute_result" 340 | } 341 | ], 342 | "source": [ 343 | "a" 344 | ] 345 | }, 346 | { 347 | "cell_type": "code", 348 | "execution_count": 13, 349 | "metadata": {}, 350 | "outputs": [ 351 | { 352 | "data": { 353 | "text/html": [ 354 | "
Unicode Characters: éΠΣ1
test22
" 355 | ], 356 | "text/plain": [ 357 | "" 358 | ] 359 | }, 360 | "execution_count": 13, 361 | "metadata": {}, 362 | "output_type": "execute_result" 363 | } 364 | ], 365 | "source": [ 366 | "apply_theme('basic')" 367 | ] 368 | }, 369 | { 370 | "cell_type": "code", 371 | "execution_count": null, 372 | "metadata": { 373 | "collapsed": true 374 | }, 375 | "outputs": [], 376 | "source": [] 377 | } 378 | ], 379 | "metadata": { 380 | "kernelspec": { 381 | "display_name": "Python [default]", 382 | "language": "python", 383 | "name": "python2" 384 | }, 385 | "language_info": { 386 | "codemirror_mode": { 387 | "name": "ipython", 388 | "version": 2 389 | }, 390 | "file_extension": ".py", 391 | "mimetype": "text/x-python", 392 | "name": "python", 393 | "nbconvert_exporter": "python", 394 | "pygments_lexer": "ipython2", 395 | "version": "2.7.13" 396 | } 397 | }, 398 | "nbformat": 4, 399 | "nbformat_minor": 1 400 | } 401 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | IPython>=1.0.0 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | """Table formatting package for IP[y] Notebooks 4 | 5 | Copyright (c) 2012-2013, ipy_table Development Team. 6 | 7 | Distributed under the terms of the Modified BSD License. 8 | 9 | The full license is in the file COPYING.txt, distributed with this software. 10 | 11 | This project is maintained at http://github.com/epmoyer/ipy_table 12 | """ 13 | 14 | from setuptools import setup, find_packages 15 | from codecs import open 16 | import os 17 | 18 | def read(fname): 19 | path = os.path.join(os.path.dirname(__file__), fname) 20 | return open(path, encoding='utf-8').read() 21 | 22 | # This will set the version string to __version__ 23 | exec(read('ipy_table/version.py')) 24 | 25 | setup( 26 | name="ipy_table", 27 | version=__version__, 28 | packages=find_packages(), 29 | 30 | # development metadata 31 | zip_safe=True, 32 | 33 | # metadata for upload to PyPI 34 | author="Eric Moyer", 35 | author_email="eric@lemoncrab.com", 36 | description="Creates richly formatted tables in a Jypyter Notebook", 37 | license="Modified BSD", 38 | keywords=['table', 'ipython', 'jupyter', 'notebook'], 39 | url='http://epmoyer.github.com/ipy_table/', 40 | classifiers=[ 41 | 'Development Status :: 5 - Production/Stable', 42 | 'Environment :: Other Environment', 43 | 'Intended Audience :: End Users/Desktop', 44 | 'Intended Audience :: Science/Research', 45 | 'License :: OSI Approved :: BSD License', 46 | 'Operating System :: OS Independent', 47 | 'Programming Language :: Python :: 2.7', 48 | 'Programming Language :: Python :: 3.3', 49 | 'Programming Language :: Python :: 3.4', 50 | 'Programming Language :: Python :: 3.5', 51 | 'Programming Language :: Python :: 3.6', 52 | 'Topic :: Text Processing :: Markup :: HTML', 53 | 'Topic :: Scientific/Engineering', 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epmoyer/ipy_table/15ee7238e5977c19b976039a6a76f2ab8ab08f03/test/__init__.py -------------------------------------------------------------------------------- /test/test_vectors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "operations": {}, 4 | "description": "Default style", 5 | "tabulate_columns": null, 6 | "result_html": "", 7 | "expected_html": "
012
101112
202122
303132
404142
", 8 | "data": [ 9 | [ 10 | 0, 11 | 1, 12 | 2 13 | ], 14 | [ 15 | 10, 16 | 11, 17 | 12 18 | ], 19 | [ 20 | 20, 21 | 21, 22 | 22 23 | ], 24 | [ 25 | 30, 26 | 31, 27 | 32 28 | ], 29 | [ 30 | 40, 31 | 41, 32 | 42 33 | ] 34 | ] 35 | }, 36 | { 37 | "operations": [ 38 | [ 39 | "apply_theme", 40 | { 41 | "theme_name": "basic" 42 | } 43 | ] 44 | ], 45 | "description": "table.apply_theme(theme_name='basic')", 46 | "tabulate_columns": null, 47 | "result_html": "", 48 | "expected_html": "
012
101112
202122
303132
404142
", 49 | "data": [ 50 | [ 51 | 0, 52 | 1, 53 | 2 54 | ], 55 | [ 56 | 10, 57 | 11, 58 | 12 59 | ], 60 | [ 61 | 20, 62 | 21, 63 | 22 64 | ], 65 | [ 66 | 30, 67 | 31, 68 | 32 69 | ], 70 | [ 71 | 40, 72 | 41, 73 | 42 74 | ] 75 | ] 76 | }, 77 | { 78 | "operations": [ 79 | [ 80 | "apply_theme", 81 | { 82 | "theme_name": "basic_left" 83 | } 84 | ] 85 | ], 86 | "description": "table.apply_theme(theme_name='basic_left')", 87 | "tabulate_columns": null, 88 | "result_html": "", 89 | "expected_html": "
012
101112
202122
303132
404142
", 90 | "data": [ 91 | [ 92 | 0, 93 | 1, 94 | 2 95 | ], 96 | [ 97 | 10, 98 | 11, 99 | 12 100 | ], 101 | [ 102 | 20, 103 | 21, 104 | 22 105 | ], 106 | [ 107 | 30, 108 | 31, 109 | 32 110 | ], 111 | [ 112 | 40, 113 | 41, 114 | 42 115 | ] 116 | ] 117 | }, 118 | { 119 | "operations": [ 120 | [ 121 | "apply_theme", 122 | { 123 | "theme_name": "basic_both" 124 | } 125 | ] 126 | ], 127 | "description": "table.apply_theme(theme_name='basic_both')", 128 | "tabulate_columns": null, 129 | "result_html": "", 130 | "expected_html": "
12
101112
202122
303132
404142
", 131 | "data": [ 132 | [ 133 | "", 134 | 1, 135 | 2 136 | ], 137 | [ 138 | 10, 139 | 11, 140 | 12 141 | ], 142 | [ 143 | 20, 144 | 21, 145 | 22 146 | ], 147 | [ 148 | 30, 149 | 31, 150 | 32 151 | ], 152 | [ 153 | 40, 154 | 41, 155 | 42 156 | ] 157 | ] 158 | }, 159 | { 160 | "operations": [ 161 | [ 162 | "set_cell_style", 163 | { 164 | "color": "red", 165 | "column": 1, 166 | "row": 1 167 | } 168 | ] 169 | ], 170 | "description": "table.set_cell_style(color='red', column=1, row=1)", 171 | "tabulate_columns": null, 172 | "result_html": "", 173 | "expected_html": "
012
101112
202122
", 174 | "data": [ 175 | [ 176 | 0, 177 | 1, 178 | 2 179 | ], 180 | [ 181 | 10, 182 | 11, 183 | 12 184 | ], 185 | [ 186 | 20, 187 | 21, 188 | 22 189 | ] 190 | ] 191 | }, 192 | { 193 | "operations": [ 194 | [ 195 | "set_row_style", 196 | { 197 | "color": "lightGreen", 198 | "row": 0 199 | } 200 | ] 201 | ], 202 | "description": "table.set_row_style(color='lightGreen', row=0)", 203 | "tabulate_columns": null, 204 | "result_html": "", 205 | "expected_html": "
012
101112
202122
", 206 | "data": [ 207 | [ 208 | 0, 209 | 1, 210 | 2 211 | ], 212 | [ 213 | 10, 214 | 11, 215 | 12 216 | ], 217 | [ 218 | 20, 219 | 21, 220 | 22 221 | ] 222 | ] 223 | }, 224 | { 225 | "operations": [ 226 | [ 227 | "set_column_style", 228 | { 229 | "color": "lightBlue", 230 | "column": 1 231 | } 232 | ] 233 | ], 234 | "description": "table.set_column_style(color='lightBlue', column=1)", 235 | "tabulate_columns": null, 236 | "result_html": "", 237 | "expected_html": "
012
101112
202122
", 238 | "data": [ 239 | [ 240 | 0, 241 | 1, 242 | 2 243 | ], 244 | [ 245 | 10, 246 | 11, 247 | 12 248 | ], 249 | [ 250 | 20, 251 | 21, 252 | 22 253 | ] 254 | ] 255 | }, 256 | { 257 | "operations": [ 258 | [ 259 | "set_global_style", 260 | { 261 | "color": "Pink" 262 | } 263 | ] 264 | ], 265 | "description": "table.set_global_style(color='Pink')", 266 | "tabulate_columns": null, 267 | "result_html": "", 268 | "expected_html": "
012
101112
202122
", 269 | "data": [ 270 | [ 271 | 0, 272 | 1, 273 | 2 274 | ], 275 | [ 276 | 10, 277 | 11, 278 | 12 279 | ], 280 | [ 281 | 20, 282 | 21, 283 | 22 284 | ] 285 | ] 286 | }, 287 | { 288 | "operations": [ 289 | [ 290 | "set_row_style", 291 | { 292 | "bold": true, 293 | "row": 1 294 | } 295 | ] 296 | ], 297 | "description": "table.set_row_style(bold=True, row=1)", 298 | "tabulate_columns": null, 299 | "result_html": "", 300 | "expected_html": "
012
101112
202122
", 301 | "data": [ 302 | [ 303 | 0, 304 | 1, 305 | 2 306 | ], 307 | [ 308 | 10, 309 | 11, 310 | 12 311 | ], 312 | [ 313 | 20, 314 | 21, 315 | 22 316 | ] 317 | ] 318 | }, 319 | { 320 | "operations": [ 321 | [ 322 | "set_row_style", 323 | { 324 | "italic": true, 325 | "row": 1 326 | } 327 | ] 328 | ], 329 | "description": "table.set_row_style(italic=True, row=1)", 330 | "tabulate_columns": null, 331 | "result_html": "", 332 | "expected_html": "
012
101112
202122
", 333 | "data": [ 334 | [ 335 | 0, 336 | 1, 337 | 2 338 | ], 339 | [ 340 | 10, 341 | 11, 342 | 12 343 | ], 344 | [ 345 | 20, 346 | 21, 347 | 22 348 | ] 349 | ] 350 | }, 351 | { 352 | "operations": [ 353 | [ 354 | "set_row_style", 355 | { 356 | "color": "Orange", 357 | "row": 1 358 | } 359 | ] 360 | ], 361 | "description": "table.set_row_style(color='Orange', row=1)", 362 | "tabulate_columns": null, 363 | "result_html": "", 364 | "expected_html": "
012
101112
202122
", 365 | "data": [ 366 | [ 367 | 0, 368 | 1, 369 | 2 370 | ], 371 | [ 372 | 10, 373 | 11, 374 | 12 375 | ], 376 | [ 377 | 20, 378 | 21, 379 | 22 380 | ] 381 | ] 382 | }, 383 | { 384 | "operations": [ 385 | [ 386 | "set_cell_style", 387 | { 388 | "thick_border": "left, top", 389 | "column": 0, 390 | "row": 0 391 | } 392 | ], 393 | [ 394 | "set_cell_style", 395 | { 396 | "thick_border": "bottom, right", 397 | "column": 2, 398 | "row": 2 399 | } 400 | ] 401 | ], 402 | "description": "Thick border, outer corners", 403 | "tabulate_columns": null, 404 | "result_html": "", 405 | "expected_html": "
012
101112
202122
", 406 | "data": [ 407 | [ 408 | 0, 409 | 1, 410 | 2 411 | ], 412 | [ 413 | 10, 414 | 11, 415 | 12 416 | ], 417 | [ 418 | 20, 419 | 21, 420 | 22 421 | ] 422 | ] 423 | }, 424 | { 425 | "operations": [ 426 | [ 427 | "set_row_style", 428 | { 429 | "thick_border": "all", 430 | "row": 1 431 | } 432 | ] 433 | ], 434 | "description": "table.set_row_style(thick_border='all', row=1)", 435 | "tabulate_columns": null, 436 | "result_html": "", 437 | "expected_html": "
012
101112
202122
", 438 | "data": [ 439 | [ 440 | 0, 441 | 1, 442 | 2 443 | ], 444 | [ 445 | 10, 446 | 11, 447 | 12 448 | ], 449 | [ 450 | 20, 451 | 21, 452 | 22 453 | ] 454 | ] 455 | }, 456 | { 457 | "operations": [ 458 | [ 459 | "set_cell_style", 460 | { 461 | "column": 0, 462 | "no_border": "left, top", 463 | "row": 0 464 | } 465 | ], 466 | [ 467 | "set_cell_style", 468 | { 469 | "column": 2, 470 | "no_border": "bottom, right", 471 | "row": 2 472 | } 473 | ] 474 | ], 475 | "description": "No border, outer corners", 476 | "tabulate_columns": null, 477 | "result_html": "", 478 | "expected_html": "
012
101112
202122
", 479 | "data": [ 480 | [ 481 | 0, 482 | 1, 483 | 2 484 | ], 485 | [ 486 | 10, 487 | 11, 488 | 12 489 | ], 490 | [ 491 | 20, 492 | 21, 493 | 22 494 | ] 495 | ] 496 | }, 497 | { 498 | "operations": [ 499 | [ 500 | "set_row_style", 501 | { 502 | "no_border": "all", 503 | "row": 1 504 | } 505 | ] 506 | ], 507 | "description": "table.set_row_style(no_border='all', row=1)", 508 | "tabulate_columns": null, 509 | "result_html": "", 510 | "expected_html": "
012
101112
202122
", 511 | "data": [ 512 | [ 513 | 0, 514 | 1, 515 | 2 516 | ], 517 | [ 518 | 10, 519 | 11, 520 | 12 521 | ], 522 | [ 523 | 20, 524 | 21, 525 | 22 526 | ] 527 | ] 528 | }, 529 | { 530 | "operations": [ 531 | [ 532 | "set_cell_style", 533 | { 534 | "column": 0, 535 | "row": 0, 536 | "row_span": 3 537 | } 538 | ] 539 | ], 540 | "description": "table.set_cell_style(column=0, row_span=3, row=0)", 541 | "tabulate_columns": null, 542 | "result_html": "", 543 | "expected_html": "
012
1112
2122
", 544 | "data": [ 545 | [ 546 | 0, 547 | 1, 548 | 2 549 | ], 550 | [ 551 | 10, 552 | 11, 553 | 12 554 | ], 555 | [ 556 | 20, 557 | 21, 558 | 22 559 | ] 560 | ] 561 | }, 562 | { 563 | "operations": [ 564 | [ 565 | "set_cell_style", 566 | { 567 | "column": 1, 568 | "column_span": 2, 569 | "row": 1 570 | } 571 | ] 572 | ], 573 | "description": "table.set_cell_style(column=1, column_span=2, row=1)", 574 | "tabulate_columns": null, 575 | "result_html": "", 576 | "expected_html": "
012
1011
202122
", 577 | "data": [ 578 | [ 579 | 0, 580 | 1, 581 | 2 582 | ], 583 | [ 584 | 10, 585 | 11, 586 | 12 587 | ], 588 | [ 589 | 20, 590 | 21, 591 | 22 592 | ] 593 | ] 594 | }, 595 | { 596 | "operations": [ 597 | [ 598 | "set_cell_style", 599 | { 600 | "column": 0, 601 | "width": 100, 602 | "row": 0 603 | } 604 | ] 605 | ], 606 | "description": "table.set_cell_style(column=0, width=100, row=0)", 607 | "tabulate_columns": null, 608 | "result_html": "", 609 | "expected_html": "
012
101112
202122
", 610 | "data": [ 611 | [ 612 | 0, 613 | 1, 614 | 2 615 | ], 616 | [ 617 | 10, 618 | 11, 619 | 12 620 | ], 621 | [ 622 | 20, 623 | 21, 624 | 22 625 | ] 626 | ] 627 | }, 628 | { 629 | "operations": [ 630 | [ 631 | "set_cell_style", 632 | { 633 | "column": 0, 634 | "width": 100, 635 | "row": 0 636 | } 637 | ], 638 | [ 639 | "set_cell_style", 640 | { 641 | "column": 0, 642 | "align": "right", 643 | "row": 0 644 | } 645 | ], 646 | [ 647 | "set_cell_style", 648 | { 649 | "column": 0, 650 | "align": "center", 651 | "row": 1 652 | } 653 | ], 654 | [ 655 | "set_cell_style", 656 | { 657 | "column": 0, 658 | "align": "left", 659 | "row": 2 660 | } 661 | ] 662 | ], 663 | "description": "Alignment (right, center, and left)", 664 | "tabulate_columns": null, 665 | "result_html": "", 666 | "expected_html": "
012
101112
202122
", 667 | "data": [ 668 | [ 669 | 0, 670 | 1, 671 | 2 672 | ], 673 | [ 674 | 10, 675 | 11, 676 | 12 677 | ], 678 | [ 679 | 20, 680 | 21, 681 | 22 682 | ] 683 | ] 684 | }, 685 | { 686 | "operations": [ 687 | [ 688 | "set_cell_style", 689 | { 690 | "wrap": true, 691 | "width": 50, 692 | "column": 0, 693 | "row": 0 694 | } 695 | ], 696 | [ 697 | "set_cell_style", 698 | { 699 | "column": 1, 700 | "widph": 50, 701 | "row": 0 702 | } 703 | ] 704 | ], 705 | "description": "Wrap", 706 | "tabulate_columns": null, 707 | "result_html": "", 708 | "expected_html": "
This cell has wrap setThis cell does not have wrap set2
101112
202122
303132
404142
", 709 | "data": [ 710 | [ 711 | "This cell has wrap set", 712 | "This cell does not have wrap set", 713 | 2 714 | ], 715 | [ 716 | 10, 717 | 11, 718 | 12 719 | ], 720 | [ 721 | 20, 722 | 21, 723 | 22 724 | ], 725 | [ 726 | 30, 727 | 31, 728 | 32 729 | ], 730 | [ 731 | 40, 732 | 41, 733 | 42 734 | ] 735 | ] 736 | }, 737 | { 738 | "operations": [ 739 | [ 740 | "set_cell_style", 741 | { 742 | "column": 0, 743 | "float_format": "%0.1f", 744 | "row": 0 745 | } 746 | ], 747 | [ 748 | "set_cell_style", 749 | { 750 | "column": 0, 751 | "float_format": "%0.6f", 752 | "row": 1 753 | } 754 | ], 755 | [ 756 | "set_cell_style", 757 | { 758 | "column": 0, 759 | "float_format": "$%0.2f", 760 | "row": 2 761 | } 762 | ] 763 | ], 764 | "description": "Float formatting", 765 | "tabulate_columns": null, 766 | "result_html": "", 767 | "expected_html": "
0.01.01012.02023.0303
10.10100011.111112.121213.1313
$20.2021.212122.222223.2323
", 768 | "data": [ 769 | [ 770 | 0.0, 771 | 1.0101, 772 | 2.0202, 773 | 3.0303 774 | ], 775 | [ 776 | 10.100999999999999, 777 | 11.111099999999999, 778 | 12.1212, 779 | 13.131300000000001 780 | ], 781 | [ 782 | 20.201999999999998, 783 | 21.2121, 784 | 22.222199999999997, 785 | 23.232300000000002 786 | ] 787 | ] 788 | }, 789 | { 790 | "operations": [ 791 | [ 792 | "set_row_style", 793 | { 794 | "float_format": "%0.2f", 795 | "row": 0 796 | } 797 | ] 798 | ], 799 | "description": "NumPy Compatibility", 800 | "tabulate_columns": null, 801 | "result_html": "", 802 | "expected_html": "
1.231.231.231.231.231.23
", 803 | "data": [ 804 | [ 805 | 1.23456789, 806 | 1.23456789, 807 | "numpy.float16(1.2344)", 808 | "numpy.float32(1.23457)", 809 | "numpy.float64(1.23456789)", 810 | "numpy.float128(1.23456789)" 811 | ] 812 | ] 813 | }, 814 | { 815 | "operations": [], 816 | "description": "Tabulate, 4 columns, without wrap", 817 | "tabulate_columns": 4, 818 | "result_html": "", 819 | "expected_html": "
0123
4567
", 820 | "data": [ 821 | 0, 822 | 1, 823 | 2, 824 | 3, 825 | 4, 826 | 5, 827 | 6, 828 | 7 829 | ] 830 | }, 831 | { 832 | "operations": [], 833 | "description": "Tabulate, 4 columns, with wrap", 834 | "tabulate_columns": 4, 835 | "result_html": "", 836 | "expected_html": "
0123
4567
8910
", 837 | "data": [ 838 | 0, 839 | 1, 840 | 2, 841 | 3, 842 | 4, 843 | 5, 844 | 6, 845 | 7, 846 | 8, 847 | 9, 848 | 10 849 | ] 850 | }, 851 | { 852 | "operations": [], 853 | "description": "Tabulate, 5 columns, with wrap", 854 | "tabulate_columns": 5, 855 | "result_html": "", 856 | "expected_html": "
01234
56789
10
", 857 | "data": [ 858 | 0, 859 | 1, 860 | 2, 861 | 3, 862 | 4, 863 | 5, 864 | 6, 865 | 7, 866 | 8, 867 | 9, 868 | 10 869 | ] 870 | }, 871 | { 872 | "operations": [], 873 | "description": "Unicode", 874 | "tabulate_columns": null, 875 | "result_html": "", 876 | "expected_html": "
Alpha:\u03b1Beta:\u03b2Gamma:\u03b3Sigma:\u03c3Pi:\u03c0
", 877 | "data": [ 878 | [ 879 | "Alpha:\u03b1", 880 | "Beta:\u03b2", 881 | "Gamma:\u03b3", 882 | "Sigma:\u03c3", 883 | "Pi:\u03c0" 884 | ] 885 | ] 886 | } 887 | ] -------------------------------------------------------------------------------- /test/test_vectors.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from ipy_table import VectorManager 4 | 5 | vector_manager = VectorManager(os.path.join('test', 'test_vectors.json')) 6 | vectors = vector_manager.vectors 7 | 8 | @pytest.mark.parametrize('vector', vectors) 9 | def test_vector(vector): 10 | vector_manager.run_vector(vector) 11 | assert vector['result_html'] == vector['expected_html'] --------------------------------------------------------------------------------