Repository: pri22296/beautifultable

Issue:
API changes regarding customizing characters

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/pri22296/beautifultable/pull/101?src=pr&el=h1) Report
> Merging [#101](https://codecov.io/gh/pri22296/beautifultable/pull/101?src=pr&el=desc) into [master](https://codecov.io/gh/pri22296/beautifultable/commit/d7b8b28253a590ba0c26dabb08303d2afd93ed63&el=desc) will **increase** coverage by `1.08%`.
> The diff coverage is `79.16%`.

[![Impacted file tree graph](https://codecov.io/gh/pri22296/beautifultable/pull/101/graphs/tree.svg?width=650&height=150&src=pr&token=JGEoYMIaay)](https://codecov.io/gh/pri22296/beautifultable/pull/101?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #101      +/-   ##
==========================================
+ Coverage   81.49%   82.58%   +1.08%     
==========================================
  Files          11       11              
  Lines        1270     1332      +62     
  Branches      208      217       +9     
==========================================
+ Hits         1035     1100      +65     
+ Misses        156      148       -8     
- Partials       79       84       +5     
```


| [Impacted Files](https://codecov.io/gh/pri22296/beautifultable/pull/101?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [beautifultable/\_\_init\_\_.py](https://codecov.io/gh/pri22296/beautifultable/pull/101/diff?src=pr&el=tree#diff-YmVhdXRpZnVsdGFibGUvX19pbml0X18ucHk=) | `100.00% <ø> (ø)` | |
| [beautifultable/utils.py](https://codecov.io/gh/pri22296/beautifultable/pull/101/diff?src=pr&el=tree#diff-YmVhdXRpZnVsdGFibGUvdXRpbHMucHk=) | `75.60% <40.00%> (-4.95%)` | :arrow_down: |
| [beautifultable/beautifultable.py](https://codecov.io/gh/pri22296/beautifultable/pull/101/diff?src=pr&el=tree#diff-YmVhdXRpZnVsdGFibGUvYmVhdXRpZnVsdGFibGUucHk=) | `84.43% <77.55%> (+3.12%)` | :arrow_up: |
| [beautifultable/helpers.py](https://codecov.io/gh/pri22296/beautifultable/pull/101/diff?src=pr&el=tree#diff-YmVhdXRpZnVsdGFibGUvaGVscGVycy5weQ==) | `75.83% <100.00%> (+0.88%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pri22296/beautifultable/pull/101?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pri22296/beautifultable/pull/101?src=pr&el=footer). Last update [d7b8b28...110e4a7](https://codecov.io/gh/pri22296/beautifultable/pull/101?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.rst b/README.rst
index 1703890..ac5eeab 100644
--- a/README.rst
+++ b/README.rst
@@ -48,6 +48,7 @@ Features included but not limited to:
 * Plenty of predefined *styles* for multiple use cases and option to
   create custom ones.
 * Support for *Unicode* characters.
+* Supports streaming table when data is slow to retrieve.
   
 .. inclusion-marker-introduction-end
 
@@ -130,7 +131,7 @@ Changelog
 **************************************************************************
 
 ==========
-v1.0.0
+Unreleased
 ==========
 
 * Added two new views ``rows`` and ``columns`` to the ``BeautifulTable`` class. Most of the existing
@@ -139,9 +140,13 @@ v1.0.0
   deprecated methods will now raise a ``FutureWarning``. Special methods such as ``__len__``, ``__iter__``,
   etc. have also been moved to the respective views. For details, refer the
   API documentation and the Updated Tutorial
+* The existing styling attributes have also been deprecated. A new ``border`` property can be accessed
+  to control all styling attributes affecting the border. Rest of the attributes can be accessed from
+  it's respective view.
 * Added support for row headers. As a result rows can now be accessed by their keys similar
   to columns
-* Added two new methods ``to_csv`` and ``from_csv`` to directly export/import using a csv file. (Thanks to @dinko-pehar)
+* Added two new methods ``to_csv`` and ``from_csv`` to directly export/import using a
+  csv file. (Thanks to `@dinko-pehar <https://github.com/dinko-pehar>`_)
 * Added ``filter`` method to generate a new table with only certain rows
 * Added a new ``shape`` attribute to the ``BeautifulTable`` class which returns a tuple of form (nrow, ncol)
 * Added alignment attribute to the ``BeautifulTable.columns.header`` object which can be used to
@@ -159,7 +164,7 @@ v1.0.0
 * Fixed an issue where table was malformed if ``blessings`` was used to generate colored strings.
 * Fixed issues with the existing implementation of ``__iter__``, ``__copy__`` and ``__deepcopy__`` which
   should now work more reliably
-* Fixed an issue where default padding could not be set to 0. (Thanks to @furlongm)
+* Fixed an issue where default padding could not be set to 0. (Thanks to `@furlongm <https://github.com/furlongm>`_)
 * Fixed several memory leak issues by ensuring that all internal objects hold only a weak reference
   to the table instance.
 * Dropped support for Python 2
diff --git a/beautifultable/__init__.py b/beautifultable/__init__.py
index d35719c..513a95e 100644
--- a/beautifultable/__init__.py
+++ b/beautifultable/__init__.py
@@ -8,6 +8,7 @@ from .beautifultable import (  # noqa F401
     BTColumnCollection,
     BTRowHeader,
     BTColumnHeader,
+    BTBorder,
     __all__,
 )
 from . import enums
diff --git a/beautifultable/beautifultable.py b/beautifultable/beautifultable.py
index b67a83d..ffcfc55 100644
--- a/beautifultable/beautifultable.py
+++ b/beautifultable/beautifultable.py
@@ -29,10 +29,18 @@ from __future__ import division, unicode_literals
 
 import copy
 import csv
+import warnings
 
 from . import enums
 
-from .utils import pre_process, termwidth, deprecated, deprecated_param
+from .utils import (
+    pre_process,
+    termwidth,
+    deprecated,
+    deprecated_param,
+    deprecation_message,
+    ensure_type,
+)
 from .compat import basestring, Iterable, to_unicode
 from .base import BTBaseList
 from .helpers import (
@@ -49,9 +57,130 @@ __all__ = [
     "BTColumnCollection",
     "BTRowHeader",
     "BTColumnHeader",
+    "BTBorder",
 ]
 
 
+class BTBorder(object):
+    """Class to control how each section of the table's border is rendered.
+
+    To disable a behaviour, just set its corresponding attribute
+    to an empty string
+
+    Attributes
+    ----------
+
+    top : str
+        Character used to draw the top border.
+
+    left : str
+        Character used to draw the left border.
+
+    bottom : str
+        Character used to draw the bottom border.
+
+    right : str
+        Character used to draw the right border.
+
+    top_left : str
+        Left most character of the top border.
+
+    bottom_left : str
+        Left most character of the bottom border.
+
+    bottom_right : str
+        Right most character of the bottom border.
+
+    top_right : str
+        Right most character of the top border.
+
+    header_left : str
+        Left most character of the header separator.
+
+    header_right : str
+        Right most character of the header separator.
+
+    top_junction : str
+        Junction character for top border.
+
+    left_junction : str
+        Left most character of the row separator.
+
+    bottom_junction : str
+        Junction character for bottom border.
+
+    right_junction : str
+        Right most character of the row separator.
+    """
+
+    def __init__(
+        self,
+        top,
+        left,
+        bottom,
+        right,
+        top_left,
+        bottom_left,
+        bottom_right,
+        top_right,
+        header_left,
+        header_right,
+        top_junction,
+        left_junction,
+        bottom_junction,
+        right_junction,
+    ):
+        self.top = left
+        self.left = right
+        self.bottom = top
+        self.right = bottom
+
+        self.top_left = top_left
+        self.bottom_left = bottom_left
+        self.bottom_right = bottom_right
+        self.top_right = top_right
+
+        self.header_left = header_left
+        self.header_right = header_right
+
+        self.top_junction = top_junction
+        self.left_junction = left_junction
+        self.bottom_junction = bottom_junction
+        self.right_junction = right_junction
+
+
+def _make_getter(attr):
+    return lambda self: getattr(self, attr)
+
+
+def _make_setter(attr):
+    return lambda self, value: setattr(
+        self, attr, ensure_type(value, basestring)
+    )
+
+
+for prop, attr in [
+    (x, "_{}".format(x))
+    for x in (
+        "top",
+        "left",
+        "bottom",
+        "right",
+        "top_left",
+        "bottom_left",
+        "bottom_right",
+        "top_right",
+        "header_left",
+        "header_right",
+        "top_junction",
+        "left_junction",
+        "bottom_junction",
+        "right_junction",
+    )
+]:
+    setattr(BTBorder, prop, property(_make_getter(attr), _make_setter(attr)))
+
+
 class BTTableData(BTBaseList):
     def __init__(self, table, value=None):
         if value is None:
@@ -68,10 +197,6 @@ class BTTableData(BTBaseList):
 
 class BeautifulTable(object):
     """Utility Class to print data in tabular format to terminal.
-    The instance attributes can be used to customize the look of the
-    table. To disable a behaviour, just set its corresponding attribute
-    to an empty string. For example, if Top border should not be drawn,
-    set `top_border_char` to ''.
 
     Parameters
     ----------
@@ -96,76 +221,10 @@ class BeautifulTable(object):
 
     Attributes
     ----------
-
-    left_border_char : str
-        Character used to draw the left border.
-
-    right_border_char : str
-        Character used to draw the right border.
-
-    top_border_char : str
-        Character used to draw the top border.
-
-    bottom_border_char : str
-        Character used to draw the bottom border.
-
-    header_separator_char : str
-        Character used to draw the line seperating Header from data.
-
-    row_separator_char : str
-        Character used to draw the line seperating two rows.
-
-    column_separator_char : str
-        Character used to draw the line seperating two columns.
-
-    intersect_top_left : str
-        Left most character of the top border.
-
-    intersect_top_mid : str
-        Intersection character for top border.
-
-    intersect_top_right : str
-        Right most character of the top border.
-
-    intersect_header_left : str
-        Left most character of the header separator.
-
-    intersect_header_mid : str
-        Intersection character for header separator.
-
-    intersect_header_right : str
-        Right most character of the header separator.
-
-    intersect_row_left : str
-        Left most character of the row separator.
-
-    intersect_row_mid : str
-        Intersection character for row separator.
-
-    intersect_row_right : str
-        Right most character of the row separator.
-
-    intersect_bottom_left : str
-        Left most character of the bottom border.
-
-    intersect_bottom_mid : str
-        Intersection character for bottom border.
-
-    intersect_bottom_right : str
-        Right most character of the bottom border.
-
     precision : int
         All float values will have maximum number of digits after the decimal,
         capped by this value(Default 3).
 
-    serialno : bool
-        Whether automatically generated serial number should be printed for
-        each row(Default False).
-
-    serialno_header : str
-        The header of the autogenerated serial number column. This value is
-        only used if serialno is True(Default SN).
-
     detect_numerics : bool
         Whether numeric strings should be automatically detected(Default True).
     """
@@ -200,8 +259,6 @@ class BeautifulTable(object):
         if kwargs["sign_mode"] is not None:
             sign = kwargs["sign_mode"]
 
-        self.set_style(enums.STYLE_DEFAULT)
-
         self.precision = precision
         self._serialno = serialno
         self._serialno_header = serialno_header
@@ -218,6 +275,13 @@ class BeautifulTable(object):
             self, default_alignment, default_padding
         )
 
+        self._header_separator = ""
+        self._header_junction = ""
+        self._column_separator = ""
+        self._row_separator = ""
+        self.border = ""
+        self.set_style(enums.STYLE_DEFAULT)
+
     def __copy__(self):
         obj = type(self)()
         obj.__dict__.update(
@@ -284,13 +348,12 @@ class BeautifulTable(object):
             "intersect_bottom_mid",
             "intersect_bottom_right",
         )
-        if to_unicode(name) in attrs and not isinstance(value, basestring):
-            value_type = type(value).__name__
-            raise TypeError(
-                (
-                    "Expected {attr} to be of type 'str', " "got '{attr_type}'"
-                ).format(attr=name, attr_type=value_type)
+        if to_unicode(name) in attrs:
+            warnings.warn(
+                deprecation_message(name, "1.0.0", "1.2.0", None),
+                FutureWarning,
             )
+            value = ensure_type(value, basestring, name)
         super(BeautifulTable, self).__setattr__(name, value)
 
     @deprecated(
@@ -337,6 +400,7 @@ class BeautifulTable(object):
     # ************************Properties Begin Here************************
     @property
     def shape(self):
+        """Read only attribute which returns the shape of the table."""
         return (len(self.rows), len(self.columns))
 
     @property
@@ -370,6 +434,44 @@ class BeautifulTable(object):
             raise ValueError(error_msg)
         self._sign = value
 
+    @property
+    def border(self):
+        """Characters used to draw the border of the table.
+
+        You can set this directly to a character or use it's several attribute
+        to control how each section of the table is rendered.
+        It is an instance of :class:`~.BTBorder`
+        """
+        return self._border
+
+    @border.setter
+    def border(self, value):
+        self._border = BTBorder(
+            top=value,
+            left=value,
+            bottom=value,
+            right=value,
+            top_left=value,
+            bottom_left=value,
+            bottom_right=value,
+            top_right=value,
+            header_left=value,
+            header_right=value,
+            top_junction=value,
+            left_junction=value,
+            bottom_junction=value,
+            right_junction=value,
+        )
+
+    @property
+    def junction(self):
+        """Character used to draw junctions in the row separator."""
+        return self._junction
+
+    @junction.setter
+    def junction(self, value):
+        self._junction = ensure_type(value, basestring)
+
     @property
     @deprecated("1.0.0", "1.2.0", BTRowCollection.header.fget)
     def serialno(self):  # pragma: no cover
@@ -408,11 +510,9 @@ class BeautifulTable(object):
         is not possible to print a given table with the width provided, this
         value will automatically adjust.
         """
-        offset = (len(self.columns) - 1) * termwidth(
-            self.column_separator_char
-        )
-        offset += termwidth(self.left_border_char)
-        offset += termwidth(self.right_border_char)
+        offset = (len(self.columns) - 1) * termwidth(self.columns.separator)
+        offset += termwidth(self.border.left)
+        offset += termwidth(self.border.right)
         self._maxwidth = max(self._maxwidth, offset + len(self.columns))
         return self._maxwidth
 
@@ -658,25 +758,29 @@ class BeautifulTable(object):
             error_msg = "allowed values for style are: " + ", ".join(allowed)
             raise ValueError(error_msg)
         style_template = style.value
-        self.left_border_char = style_template.left_border_char
-        self.right_border_char = style_template.right_border_char
-        self.top_border_char = style_template.top_border_char
-        self.bottom_border_char = style_template.bottom_border_char
-        self.header_separator_char = style_template.header_separator_char
-        self.column_separator_char = style_template.column_separator_char
-        self.row_separator_char = style_template.row_separator_char
-        self.intersect_top_left = style_template.intersect_top_left
-        self.intersect_top_mid = style_template.intersect_top_mid
-        self.intersect_top_right = style_template.intersect_top_right
-        self.intersect_header_left = style_template.intersect_header_left
-        self.intersect_header_mid = style_template.intersect_header_mid
-        self.intersect_header_right = style_template.intersect_header_right
-        self.intersect_row_left = style_template.intersect_row_left
-        self.intersect_row_mid = style_template.intersect_row_mid
-        self.intersect_row_right = style_template.intersect_row_right
-        self.intersect_bottom_left = style_template.intersect_bottom_left
-        self.intersect_bottom_mid = style_template.intersect_bottom_mid
-        self.intersect_bottom_right = style_template.intersect_bottom_right
+        self.border.left = style_template.left_border_char
+        self.border.right = style_template.right_border_char
+        self.border.top = style_template.top_border_char
+        self.border.bottom = style_template.bottom_border_char
+
+        self.border.top_left = style_template.intersect_top_left
+        self.border.bottom_left = style_template.intersect_bottom_left
+        self.border.bottom_right = style_template.intersect_bottom_right
+        self.border.top_right = style_template.intersect_top_right
+
+        self.border.header_left = style_template.intersect_header_left
+        self.border.header_right = style_template.intersect_header_right
+
+        self.columns.header.separator = style_template.header_separator_char
+        self.columns.separator = style_template.column_separator_char
+        self.rows.separator = style_template.row_separator_char
+
+        self.border.top_junction = style_template.intersect_top_mid
+        self.border.left_junction = style_template.intersect_row_left
+        self.border.bottom_junction = style_template.intersect_bottom_mid
+        self.border.right_junction = style_template.intersect_row_right
+        self.columns.header.junction = style_template.intersect_header_mid
+        self.junction = style_template.intersect_row_mid
 
     def _compute_width(self):
         """Calculate width of column automatically based on data."""
@@ -834,20 +938,19 @@ class BeautifulTable(object):
         if not char.isspace():
             # If left border is enabled and it is visible
             visible_junc = not intersect_left.isspace()
-            if termwidth(self.left_border_char) > 0:
-                if not (self.left_border_char.isspace() and visible_junc):
+            if termwidth(self.border.left) > 0:
+                if not (self.border.left.isspace() and visible_junc):
                     length = min(
-                        termwidth(self.left_border_char),
-                        termwidth(intersect_left),
+                        termwidth(self.border.left), termwidth(intersect_left),
                     )
                     for i in range(length):
                         line[i] = intersect_left[i] if mask[0] else " "
             visible_junc = not intersect_right.isspace()
             # If right border is enabled and it is visible
-            if termwidth(self.right_border_char) > 0:
-                if not (self.right_border_char.isspace() and visible_junc):
+            if termwidth(self.border.right) > 0:
+                if not (self.border.right.isspace() and visible_junc):
                     length = min(
-                        termwidth(self.right_border_char),
+                        termwidth(self.border.right),
                         termwidth(intersect_right),
                     )
                     for i in range(length):
@@ -856,16 +959,16 @@ class BeautifulTable(object):
                         )
             visible_junc = not intersect_mid.isspace()
             # If column separator is enabled and it is visible
-            if termwidth(self.column_separator_char):
-                if not (self.column_separator_char.isspace() and visible_junc):
-                    index = termwidth(self.left_border_char)
+            if termwidth(self.columns.separator):
+                if not (self.columns.separator.isspace() and visible_junc):
+                    index = termwidth(self.border.left)
                     for i in range(len(self.columns) - 1):
                         if not mask[i]:
                             for j in range(self.columns.width[i]):
                                 line[index + j] = " "
                         index += self.columns.width[i]
                         length = min(
-                            termwidth(self.column_separator_char),
+                            termwidth(self.columns.separator),
                             termwidth(intersect_mid),
                         )
                         for j in range(length):
@@ -875,46 +978,46 @@ class BeautifulTable(object):
                                 if (mask[i] or mask[i + 1])
                                 else " "
                             )
-                        index += termwidth(self.column_separator_char)
+                        index += termwidth(self.columns.separator)
 
         return "".join(line)
 
     def _get_top_border(self, *args, **kwargs):
         return self._get_horizontal_line(
-            self.top_border_char,
-            self.intersect_top_left,
-            self.intersect_top_mid,
-            self.intersect_top_right,
+            self.border.top,
+            self.border.top_left,
+            self.border.top_junction,
+            self.border.top_right,
             *args,
             **kwargs
         )
 
     def _get_header_separator(self, *args, **kwargs):
         return self._get_horizontal_line(
-            self.header_separator_char,
-            self.intersect_header_left,
-            self.intersect_header_mid,
-            self.intersect_header_right,
+            self.columns.header.separator,
+            self.border.header_left,
+            self.columns.header.junction,
+            self.border.header_right,
             *args,
             **kwargs
         )
 
     def _get_row_separator(self, *args, **kwargs):
         return self._get_horizontal_line(
-            self.row_separator_char,
-            self.intersect_row_left,
-            self.intersect_row_mid,
-            self.intersect_row_right,
+            self.rows.separator,
+            self.border.left_junction,
+            self.junction,
+            self.border.right_junction,
             *args,
             **kwargs
         )
 
     def _get_bottom_border(self, *args, **kwargs):
         return self._get_horizontal_line(
-            self.bottom_border_char,
-            self.intersect_bottom_left,
-            self.intersect_bottom_mid,
-            self.intersect_bottom_right,
+            self.border.bottom,
+            self.border.bottom_left,
+            self.border.bottom_junction,
+            self.border.bottom_right,
             *args,
             **kwargs
         )
@@ -933,18 +1036,16 @@ class BeautifulTable(object):
         if len(self.columns) == 0:
             return 0
         width = sum(self.columns.width)
-        width += (len(self.columns) - 1) * termwidth(
-            self.column_separator_char
-        )
-        width += termwidth(self.left_border_char)
-        width += termwidth(self.right_border_char)
+        width += (len(self.columns) - 1) * termwidth(self.columns.separator)
+        width += termwidth(self.border.left)
+        width += termwidth(self.border.right)
         return width
 
     @deprecated("1.0.0", "1.2.0", width.fget)
     def get_table_width(self):  # pragma: no cover
         return self.width
 
-    def _get_string(self, rows, append=False, recalculate_width=True):
+    def _get_string(self, rows=None, append=False, recalculate_width=True):
         row_header_visible = bool(
             "".join(
                 x if x is not None else "" for x in self.rows.header
@@ -966,7 +1067,7 @@ class BeautifulTable(object):
             if row_header_visible:
                 self.columns.insert(0, self.rows.header)
 
-        if len(self.rows) > 0:
+        if len(self.rows) > 0 or rows is not None:
             if column_header_visible:
                 self.rows.insert(0, self.columns.header)
 
@@ -993,43 +1094,48 @@ class BeautifulTable(object):
 
         try:
             # Rendering the top border
-            if self.top_border_char:
+            if self.border.top:
                 yield self._get_top_border()
 
             # Print column headers if not empty or only spaces
             row_iterator = iter(self.rows)
             if column_header_visible:
-                yield next(row_iterator)._get_string(
-                    align=self.columns.header.alignment
-                )
-                if self.header_separator_char:
-                    yield self._get_header_separator()
+                if len(self.rows) > 1 or rows is not None:
+                    yield next(row_iterator)._get_string(
+                        align=self.columns.header.alignment
+                    )
+                    if self.columns.header.separator:
+                        yield self._get_header_separator()
 
             # Printing rows
             first_row_encountered = False
             for i, row in enumerate(row_iterator):
-                if first_row_encountered and self.row_separator_char:
+                if first_row_encountered and self.rows.separator:
                     yield self._get_row_separator()
                 first_row_encountered = True
                 content = to_unicode(row)
                 yield content
 
-            # Printing additional rows
-            prev_length = len(self.rows)
-            for i, row in enumerate(rows, start=1):
-                if first_row_encountered and self.row_separator_char:
-                    yield self._get_row_separator()
-                first_row_encountered = True
-                if self._serialno:
-                    row.insert(0, prev_length + i)
-                self.rows.append(row)
-                content = to_unicode(self.rows[-1])
-                if not append:
-                    self.rows.pop()
-                yield content
+            if rows is not None:
+                # Printing additional rows
+                prev_length = len(self.rows)
+                for i, row in enumerate(rows, start=1):
+                    if first_row_encountered and self.rows.separator:
+                        yield self._get_row_separator()
+                    first_row_encountered = True
+                    if self._serialno:
+                        row.insert(0, prev_length + i)
+                    if len(self.columns) > 1 and row_header_visible:
+                        self.rows.append([None] + list(row))
+                    else:
+                        self.rows.append(row)
+                    content = to_unicode(self.rows[-1])
+                    if not append:
+                        self.rows.pop()
+                    yield content
 
             # Rendering the bottom border
-            if self.bottom_border_char:
+            if self.border.bottom:
                 yield self._get_bottom_border()
         except Exception:
             raise
@@ -1045,14 +1151,15 @@ class BeautifulTable(object):
 
                 if self._serialno:
                     self.columns.pop(0)
+        return
 
     def stream(self, rows, append=False):
         """Get a generator for the table.
 
-        This should be used in cases where data takes time to retrieve and
-        it is required to be displayed as soon as possible. Any existing rows
-        in the table shall also be returned. It is essential that atleast one
-        of title, width or existing rows set prior to calling this method.
+        This should be used in cases where data takes time to retrieve and it
+        is required to be displayed as soon as possible. Any existing rows in
+        the table shall also be returned. It is essential that atleast one of
+        column header, width or existing rows set before calling this method.
 
         Parameters
         ----------
diff --git a/beautifultable/helpers.py b/beautifultable/helpers.py
index 5cf079e..bae010d 100644
--- a/beautifultable/helpers.py
+++ b/beautifultable/helpers.py
@@ -4,7 +4,7 @@ import operator
 
 from . import enums
 from .base import BTBaseRow, BTBaseColumn
-from .utils import pre_process, termwidth, textwrap
+from .utils import pre_process, termwidth, textwrap, ensure_type
 from .compat import basestring, Iterable, to_unicode, zip_longest
 from .meta import AlignmentMetaData, NonNegativeIntegerMetaData
 
@@ -56,6 +56,24 @@ class BTColumnHeader(BTBaseRow):
             value = [value] * len(self)
         self._alignment = AlignmentMetaData(self._table, value)
 
+    @property
+    def separator(self):
+        """Character used to draw the line seperating header from the table."""
+        return self._table._header_separator
+
+    @separator.setter
+    def separator(self, value):
+        self._table._header_separator = ensure_type(value, basestring)
+
+    @property
+    def junction(self):
+        """Character used to draw junctions in the header separator."""
+        return self._table._header_junction
+
+    @junction.setter
+    def junction(self, value):
+        self._table._header_junction = ensure_type(value, basestring)
+
     def __setitem__(self, key, value):
         self._validate_item(value)
         super(BTColumnHeader, self).__setitem__(key, value)
@@ -243,22 +261,22 @@ class BTRowData(BTBaseRow):
                 for j, item in enumerate(row_):
                     if j > 0:
                         content.append(
-                            table.column_separator_char
+                            table.columns.separator
                             if (mask[j - 1] or mask[j])
-                            else " " * termwidth(table.column_separator_char)
+                            else " " * termwidth(table.columns.separator)
                         )
                     content.append(item)
                 # content = table.column_separator_char.join(row_)
                 content = "".join(content)
                 content = (
-                    table.left_border_char
+                    table.border.left
                     if mask[0]
-                    else " " * termwidth(table.left_border_char)
+                    else " " * termwidth(table.border.left)
                 ) + content
                 content += (
-                    table.right_border_char
+                    table.border.right
                     if mask[-1]
-                    else " " * termwidth(table.right_border_char)
+                    else " " * termwidth(table.border.right)
                 )
                 string.append(content)
         return "\n".join(string)
@@ -302,6 +320,15 @@ class BTRowCollection(object):
     def header(self, value):
         self._header = BTRowHeader(self._table, value)
 
+    @property
+    def separator(self):
+        """Character used to draw the line seperating two rows."""
+        return self._table._row_separator
+
+    @separator.setter
+    def separator(self, value):
+        self._table._row_separator = ensure_type(value, basestring)
+
     def _canonical_key(self, key):
         if isinstance(key, (int, slice)):
             return key
@@ -817,6 +844,15 @@ class BTColumnCollection(object):
         else:
             self._default_padding = value
 
+    @property
+    def separator(self):
+        """Character used to draw the line seperating two columns."""
+        return self._table._column_separator
+
+    @separator.setter
+    def separator(self, value):
+        self._table._column_separator = ensure_type(value, basestring)
+
     def __len__(self):
         return self._table._ncol
 
diff --git a/beautifultable/utils.py b/beautifultable/utils.py
index da0a87a..69e4893 100644
--- a/beautifultable/utils.py
+++ b/beautifultable/utils.py
@@ -36,6 +36,17 @@ def to_numeric(item):
         return num
 
 
+def ensure_type(value, *types, varname="value"):
+    if not isinstance(value, types):
+        expected_types_str = "/".join([t.__name__ for t in types])
+        raise TypeError(
+            ("Expected '{}' to be of type '{}', " "got '{}'").format(
+                varname, expected_types_str, type(value).__name__
+            )
+        )
+    return value
+
+
 def pre_process(item, detect_numerics, precision, sign_value):
     """Returns the final string which should be displayed"""
     if item is None:
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 4fd0a66..df20f97 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -37,8 +37,39 @@ Let's create our first table and add some rows.
    | S5 | Michael  |  3   |  boy   |
    +----+----------+------+--------+
 
-We created our first table. Let's add some more data to it.
-But this time we will add a new column.
+BeautifulTable initializes the shape lazily. Here when you appended the first row,
+the number of columns was set to 3. Further rows had to be of length 3. If you had
+set the columns and/or row headers beforehand as follows, the table shape would already be
+set to (5, 3). Hence you would just set the rows directly using their indices or keys.
+
+.. code:: python
+
+   >>> from beautifultable import BeautifulTable
+   >>> table = BeautifulTable()
+   >>> table.columns.header = ["name", "rank", "gender"]
+   >>> table.rows.header = ["S1", "S2", "S3", "S4", "S5"]
+   >>> table.rows[0] = ["Jacob", 1, "boy"]
+   >>> table.rows[1] = ["Isabella", 1, "girl"]
+   >>> table.rows[2] = ["Ethan", 2, "boy"]
+   >>> table.rows[3] = ["Sophia", 2, "girl"]
+   >>> table.rows[4]  =["Michael", 3, "boy"]
+   >>> print(table)
+   +----+----------+------+--------+
+   |    |   name   | rank | gender |
+   +----+----------+------+--------+
+   | S1 |  Jacob   |  1   |  boy   |
+   +----+----------+------+--------+
+   | S2 | Isabella |  1   |  girl  |
+   +----+----------+------+--------+
+   | S3 |  Ethan   |  2   |  boy   |
+   +----+----------+------+--------+
+   | S4 |  Sophia  |  2   |  girl  |
+   +----+----------+------+--------+
+   | S5 | Michael  |  3   |  boy   |
+   +----+----------+------+--------+
+
+
+So, We created our first table. Let's add a new column.
 
 .. code:: python
 
@@ -805,6 +836,46 @@ works.
    | Isabella | 1 | girl |                     |
    +---------------------+---------------------+
 
+=========================================================================
+Streaming Tables
+=========================================================================
+
+There are situations where data retrieval is slow such as when data is
+recieved over a network and you want to display the data as soon as
+possible. In these cases, you can use streaming tables to render the table
+with the help of a generator.
+
+Streaming table do have their limitation. The width calculation routine
+requires you to either set it manually or specify the column header or
+add atleast 1 row. You also cannot have row headers for streaming tables.
+
+.. code:: python
+
+   >>> import time
+   >>> def time_taking_process():
+   ...     for i in range(5):
+   ...         time.sleep(1)
+   ...         yield [i, i**2]
+   ...
+   ...
+   >>> table = BeautifulTable()
+   >>> table.columns.header = ["Number", "It's Square"]
+   >>> for line in table.stream(time_taking_process()):
+   ...     print(line)
+   ...
+   +--------+-------------+
+   | Number | It's Square |
+   +--------+-------------+
+   |   0    |      0      |
+   +--------+-------------+
+   |   1    |      1      |
+   +--------+-------------+
+   |   2    |      4      |
+   +--------+-------------+
+   |   3    |      9      |
+   +--------+-------------+
+   |   4    |     16      |
+   +--------+-------------+
 
 =========================================================================
 Support for Multibyte Unicode characters
@@ -813,7 +884,6 @@ Support for Multibyte Unicode characters
 **beautifultable** comes with built-in support for multibyte unicode such as
 east-asian characters.
 
-
 You can do much more with BeautifulTable but this much should give you a
 good start. Those of you who are interested to have more control can
 read the API Documentation.
diff --git a/docs/source/beautifultable.rst b/docs/source/beautifultable.rst
index 17f55da..0ea786d 100644
--- a/docs/source/beautifultable.rst
+++ b/docs/source/beautifultable.rst
@@ -28,4 +28,7 @@ Module contents
 .. autoclass:: beautifultable.BTColumnHeader
     :members:
     :special-members: __getitem__,  __setitem__
-    :inherited-members:
\ No newline at end of file
+    :inherited-members:
+
+.. autoclass:: beautifultable.BTBorder
+    :members:
\ No newline at end of file


Test patch:
diff --git a/test.py b/test.py
index 8211d80..93d1fca 100644
--- a/test.py
+++ b/test.py
@@ -1,8 +1,9 @@
 # -*- coding: utf-8 -*-
 
 
-import unittest
 import os
+import unittest
+import itertools
 
 from beautifultable import BeautifulTable
 
@@ -26,7 +27,7 @@ class TableOperationsTestCase(unittest.TestCase):
         self.table = table
 
     def compare_iterable(self, iterable1, iterable2):
-        for item1, item2 in zip(iterable1, iterable2):
+        for item1, item2 in itertools.zip_longest(iterable1, iterable2):
             self.assertEqual(item1, item2)
 
     # Test for table operations
@@ -370,6 +371,32 @@ class TableOperationsTestCase(unittest.TestCase):
 +----+----------+------+--------+"""
         self.assertEqual(string, self.table.get_string())
 
+    def test_stream(self):
+        def generator():
+            for i in range(1, 6):
+                yield [i, i ** 2]
+
+        table = BeautifulTable()
+        table.columns.header = ["Number", "It's Square"]
+        self.compare_iterable(
+            table.stream(generator()),
+            [
+                "+--------+-------------+",
+                "| Number | It's Square |",
+                "+--------+-------------+",
+                "|   1    |      1      |",
+                "+--------+-------------+",
+                "|   2    |      4      |",
+                "+--------+-------------+",
+                "|   3    |      9      |",
+                "+--------+-------------+",
+                "|   4    |     16      |",
+                "+--------+-------------+",
+                "|   5    |     25      |",
+                "+--------+-------------+",
+            ],
+        )
+
     def test_left_align(self):
         self.table.columns.alignment[0] = self.table.ALIGN_LEFT
         string = """+----+----------+------+--------+