Real Python Pandas Groupby Tutorial

In [1]:
import pandas as pd
In [2]:
# 3 decimal places in output display
pd.set_option("display.precision", 3)
In [3]:
# Don't wrap repr(DataFrame) accross additional lines

pd.set_option("display.expand_frame_repr", False)
In [4]:
# Set max rows displayed in output to 25
pd.set_option("display.max_rows", 25)
In [14]:
# Download datasets
import urllib.request
from pathlib import Path
import os

with urllib.request.urlopen(
    urllib.request.Request(
        "https://github.com/realpython/materials/raw/master/pandas-groupby/groupby-data.zip",
        headers={
            "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0"
        },
    )
) as fh:
    Path(os.curdir, "groupby-data.zip").write_bytes(fh.read())
In [ ]:
 

Explore HTML Tools Chamelboots

Explore the html and datautil modules added to chamelboots.

Verify what exactly the attribute sourceline is in lxml. Does it correspond in any way to the raw html source?

In [1]:
from lxml import etree
In [2]:
from chamelboots.html.packages import bootstrap
from chamelboots.constants import HTML_PARSER
from chamelboots.html import get_html_as_data
from chamelboots.datautil import get_from, paths_in_data
In [3]:
[item for item in dir(bootstrap) if not item.startswith("_")]
Out[3]:
['starter_html']
In [4]:
html_element = (
    etree.fromstring(bootstrap.starter_html, HTML_PARSER).getroottree().getroot()
)
In [5]:
{
    tuple(item for item in dir(element) if not item.startswith("_"))
    for element in html_element.iterdescendants()
}
Out[5]:
{('addnext',
  'addprevious',
  'append',
  'attrib',
  'base',
  'clear',
  'cssselect',
  'extend',
  'find',
  'findall',
  'findtext',
  'get',
  'getchildren',
  'getiterator',
  'getnext',
  'getparent',
  'getprevious',
  'getroottree',
  'index',
  'insert',
  'items',
  'iter',
  'iterancestors',
  'iterchildren',
  'iterdescendants',
  'iterfind',
  'itersiblings',
  'itertext',
  'keys',
  'makeelement',
  'nsmap',
  'prefix',
  'remove',
  'replace',
  'set',
  'sourceline',
  'tag',
  'tail',
  'text',
  'values',
  'xpath')}
In [6]:
html_element.sourceline
Out[6]:
2
In [7]:
[(i, line) for i, line in enumerate(bootstrap.starter_html.splitlines())]
Out[7]:
[(0, '<!doctype html>'),
 (1, '<html lang="en">'),
 (2, '  <head>'),
 (3, '    <!-- Required meta tags -->'),
 (4, '    <meta charset="utf-8">'),
 (5,
  '    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">'),
 (6, '    <!-- Bootstrap CSS -->'),
 (7, '    <link'),
 (8, '    rel="stylesheet"'),
 (9,
  '    href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"'),
 (10,
  '    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">'),
 (11, '    <!-- Optional JavaScript -->'),
 (12, '    <!-- jQuery first, then Popper.js, then Bootstrap JS -->'),
 (13, '    <script'),
 (14, '    defer="defer"'),
 (15, '    src="https://code.jquery.com/jquery-3.3.1.slim.min.js"'),
 (16,
  '    integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"'),
 (17, '    crossorigin="anonymous"></script>'),
 (18, '    <script'),
 (19, '    defer="defer"'),
 (20,
  '    src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"'),
 (21,
  '    integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"'),
 (22, '    crossorigin="anonymous"></script>'),
 (23, '    <script'),
 (24, '    defer="defer"'),
 (25,
  '    src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"'),
 (26,
  '    integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"'),
 (27, '    crossorigin="anonymous"></script>'),
 (28, '    <title>Bootstrap title</title>'),
 (29, '  </head>'),
 (30, '  <body>'),
 (31, '    <div>'),
 (32, '        <h1>Hello, world!</h1>'),
 (33, '    </div>'),
 (34, '  </body>'),
 (35, '</html>')]
In [8]:
[
    (element, element.sourceline,)
    for element in html_element.iterdescendants()
    if element.tag == "meta"
]
Out[8]:
[(<Element meta at 0x7f137b6cdf50>, 5), (<Element meta at 0x7f137b6cd460>, 6)]
In [9]:
[
    (i, line)
    for i, line in enumerate(bootstrap.starter_html.splitlines())
    if "meta" in line
]
Out[9]:
[(3, '    <!-- Required meta tags -->'),
 (4, '    <meta charset="utf-8">'),
 (5,
  '    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">')]

Conclusion: The sourceline attribute in an lxml element doesn't correspond with the source line of the raw html.

HTML in a Pandas dataframe

In [10]:
import itertools as it
import operator as op
In [11]:
import pandas as pd
from IPython.display import display, HTML
In [12]:
data = get_html_as_data(bootstrap.starter_html)
In [13]:
paths = paths_in_data(data)
In [14]:
{len(path) for path in paths}
Out[14]:
{2, 5, 8, 11}
In [15]:
by_first = op.itemgetter(0)
dfs = [
    pd.DataFrame([op.add(p, (repr(get_from(data, p)),)) for l, p in group])
    for key, group in it.groupby(
        sorted([(len(path), path) for path in paths], key=by_first), key=by_first,
    )
]

Set indices on dataframes to the tag names which are at -3 in columns.

In [16]:
for df in dfs:

    stop = i if (i := df.columns.stop - 3) else len(df.columns)
    df.index = [list(row)[-1] for i, row in df.loc[:, :stop].iterrows()]
In [17]:
for df in dfs:
    try:
        display(df.loc[("script", "link"), :])
    except KeyError:
        pass
0 1 2 3 4 5 6 7 8
script html inner_content 0 head inner_content 7 script inner_content None
script html inner_content 0 head inner_content 7 script attributes {'defer': 'defer', 'src': 'https://code.jquery...
script html inner_content 0 head inner_content 7 script tail '\n '
script html inner_content 0 head inner_content 8 script inner_content None
script html inner_content 0 head inner_content 8 script attributes {'defer': 'defer', 'src': 'https://cdnjs.cloud...
script html inner_content 0 head inner_content 8 script tail '\n '
script html inner_content 0 head inner_content 9 script inner_content None
script html inner_content 0 head inner_content 9 script attributes {'defer': 'defer', 'src': 'https://stackpath.b...
script html inner_content 0 head inner_content 9 script tail '\n '
link html inner_content 0 head inner_content 4 link inner_content None
link html inner_content 0 head inner_content 4 link attributes {'rel': 'stylesheet', 'href': 'https://stackpa...
link html inner_content 0 head inner_content 4 link tail '\n '
In [18]:
for df in dfs:
    display(HTML(df.to_html()))
0 1 2
{'lang': 'en'} html attribs {'lang': 'en'}
None html tail None
0 1 2 3 4 5
head html inner_content 0 head attributes {}
head html inner_content 0 head tail '\n '
body html inner_content 1 body attributes {}
body html inner_content 1 body tail '\n'
0 1 2 3 4 5 6 7 8
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 0 <cyfunction Comment at 0x7f1398068ae0> inner_content ' Required meta tags '
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 0 <cyfunction Comment at 0x7f1398068ae0> attributes <lxml.etree._ImmutableMapping object at 0x7f139804fc30>
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 0 <cyfunction Comment at 0x7f1398068ae0> tail '\n '
meta html inner_content 0 head inner_content 1 meta inner_content None
meta html inner_content 0 head inner_content 1 meta attributes {'charset': 'utf-8'}
meta html inner_content 0 head inner_content 1 meta tail '\n '
meta html inner_content 0 head inner_content 2 meta inner_content None
meta html inner_content 0 head inner_content 2 meta attributes {'name': 'viewport', 'content': 'width=device-width, initial-scale=1, shrink-to-fit=no'}
meta html inner_content 0 head inner_content 2 meta tail '\n '
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 3 <cyfunction Comment at 0x7f1398068ae0> inner_content ' Bootstrap CSS '
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 3 <cyfunction Comment at 0x7f1398068ae0> attributes <lxml.etree._ImmutableMapping object at 0x7f139804fc30>
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 3 <cyfunction Comment at 0x7f1398068ae0> tail '\n '
link html inner_content 0 head inner_content 4 link inner_content None
link html inner_content 0 head inner_content 4 link attributes {'rel': 'stylesheet', 'href': 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css', 'integrity': 'sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T', 'crossorigin': 'anonymous'}
link html inner_content 0 head inner_content 4 link tail '\n '
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 5 <cyfunction Comment at 0x7f1398068ae0> inner_content ' Optional JavaScript '
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 5 <cyfunction Comment at 0x7f1398068ae0> attributes <lxml.etree._ImmutableMapping object at 0x7f139804fc30>
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 5 <cyfunction Comment at 0x7f1398068ae0> tail '\n '
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 6 <cyfunction Comment at 0x7f1398068ae0> inner_content ' jQuery first, then Popper.js, then Bootstrap JS '
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 6 <cyfunction Comment at 0x7f1398068ae0> attributes <lxml.etree._ImmutableMapping object at 0x7f139804fc30>
<cyfunction Comment at 0x7f1398068ae0> html inner_content 0 head inner_content 6 <cyfunction Comment at 0x7f1398068ae0> tail '\n '
script html inner_content 0 head inner_content 7 script inner_content None
script html inner_content 0 head inner_content 7 script attributes {'defer': 'defer', 'src': 'https://code.jquery.com/jquery-3.3.1.slim.min.js', 'integrity': 'sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo', 'crossorigin': 'anonymous'}
script html inner_content 0 head inner_content 7 script tail '\n '
script html inner_content 0 head inner_content 8 script inner_content None
script html inner_content 0 head inner_content 8 script attributes {'defer': 'defer', 'src': 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js', 'integrity': 'sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1', 'crossorigin': 'anonymous'}
script html inner_content 0 head inner_content 8 script tail '\n '
script html inner_content 0 head inner_content 9 script inner_content None
script html inner_content 0 head inner_content 9 script attributes {'defer': 'defer', 'src': 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js', 'integrity': 'sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM', 'crossorigin': 'anonymous'}
script html inner_content 0 head inner_content 9 script tail '\n '
title html inner_content 0 head inner_content 10 title inner_content 'Bootstrap title'
title html inner_content 0 head inner_content 10 title attributes {}
title html inner_content 0 head inner_content 10 title tail '\n '
div html inner_content 1 body inner_content 0 div attributes {}
div html inner_content 1 body inner_content 0 div tail '\n '
0 1 2 3 4 5 6 7 8 9 10 11
h1 html inner_content 1 body inner_content 0 div inner_content 0 h1 inner_content 'Hello, world!'
h1 html inner_content 1 body inner_content 0 div inner_content 0 h1 attributes {}
h1 html inner_content 1 body inner_content 0 div inner_content 0 h1 tail '\n '

Create HTML with python-chamelboots: An Experiment

Experiment with python-chamelboots to create HTML.

Resources

Replicate an HTML document using chamelboots.

Specs

Replace the rel and integrity attributes in the link tag and the src and integrity attributes in the script tag with different values without editing the starter_html string.

The new result should be a list of strings that would replace a range of lines in starter_html.

In [1]:
from chamelboots.constants import HTML_PARSER, Join
from chamelboots import ChameleonTemplate as CT
from chamelboots import TalStatement as TS
In [2]:
from functools import reduce
import operator as op
from pprint import pprint
import itertools as it
from subprocess import check_call
import shlex
from pathlib import Path
import tempfile
In [3]:
from lxml import etree
from bs4 import BeautifulSoup
from IPython.display import display, IFrame
In [4]:
starter_html = """<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script defer="defer" src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script defer="defer" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script defer="defer" src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
    <title>Bootstrap title</title>
  </head>
  <body>
    <div>
        <h1>Hello, world!{nested_span}</h1>
        {list_}
    </div>
  </body>
</html>""".format(  # add some extra HTML using chamelboots
    list_=CT(
        "ul", (TS("content", "structure content"), TS("attributes", "attributes"))
    ).render(
        attributes={"class": "list-group"},
        content=CT(
            "li",
            (TS("repeat", "item items"), TS("attributes", "attributes")),
            "${item}",
        ).render(
            items=(f"foo item number {i}" for i in range(10)),
            attributes={"class": "list-group-item"},
        ),
    ),
    nested_span=CT("span", (), "I am a nested span."),
)
print(BeautifulSoup(starter_html, "html.parser").prettify())
<!DOCTYPE doctype html>
<html lang="en">
 <head>
  <!-- Required meta tags -->
  <meta charset="utf-8"/>
  <meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport"/>
  <!-- Bootstrap CSS -->
  <link crossorigin="anonymous" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" rel="stylesheet"/>
  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script crossorigin="anonymous" defer="defer" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" src="https://code.jquery.com/jquery-3.3.1.slim.min.js">
  </script>
  <script crossorigin="anonymous" defer="defer" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js">
  </script>
  <script crossorigin="anonymous" defer="defer" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js">
  </script>
  <title>
   Bootstrap title
  </title>
 </head>
 <body>
  <div>
   <h1>
    Hello, world!
    <span>
     I am a nested span.
    </span>
   </h1>
   <ul class="list-group">
    <li class="list-group-item">
     foo item number 0
    </li>
    <li class="list-group-item">
     foo item number 1
    </li>
    <li class="list-group-item">
     foo item number 2
    </li>
    <li class="list-group-item">
     foo item number 3
    </li>
    <li class="list-group-item">
     foo item number 4
    </li>
    <li class="list-group-item">
     foo item number 5
    </li>
    <li class="list-group-item">
     foo item number 6
    </li>
    <li class="list-group-item">
     foo item number 7
    </li>
    <li class="list-group-item">
     foo item number 8
    </li>
    <li class="list-group-item">
     foo item number 9
    </li>
   </ul>
  </div>
 </body>
</html>

Upload starter_html to my static webserver to display in an IFrame

In [5]:
def save_to_minio(text):
    tmpfile = Path(tempfile.mkstemp(suffix=".html")[-1])
    tmpfile.write_text(text)
    url = f"https://minio.apps.selfip.com/mymedia/html/{tmpfile.name}"
    check_call(shlex.split(f"mc cp {tmpfile} dokkuminio/mymedia/html/"))
    return url

Display template HTML document.

In [6]:
url = save_to_minio(starter_html)
print(url)
display(IFrame(src=url, width="auto", height=500))
https://minio.apps.selfip.com/mymedia/html/tmpixne_sks.html
In [7]:
tree = etree.fromstring(starter_html, HTML_PARSER)

Flat structure.

Flat is better than nested. Without nesting it makes it difficult to reconstruct the original HTML.

In [8]:
groups = [
    (e.tag, tuple(e.attrib.items()), e.text.strip() if e.text is not None else "")
    for e in tree.iter()
    if isinstance(e.tag, str)
]
groups
Out[8]:
[('html', (('lang', 'en'),), ''),
 ('head', (), ''),
 ('meta', (('charset', 'utf-8'),), ''),
 ('meta',
  (('name', 'viewport'),
   ('content', 'width=device-width, initial-scale=1, shrink-to-fit=no')),
  ''),
 ('link',
  (('rel', 'stylesheet'),
   ('href',
    'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'),
   ('integrity',
    'sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T'),
   ('crossorigin', 'anonymous')),
  ''),
 ('script',
  (('defer', 'defer'),
   ('src', 'https://code.jquery.com/jquery-3.3.1.slim.min.js'),
   ('integrity',
    'sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo'),
   ('crossorigin', 'anonymous')),
  ''),
 ('script',
  (('defer', 'defer'),
   ('src',
    'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js'),
   ('integrity',
    'sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1'),
   ('crossorigin', 'anonymous')),
  ''),
 ('script',
  (('defer', 'defer'),
   ('src',
    'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js'),
   ('integrity',
    'sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM'),
   ('crossorigin', 'anonymous')),
  ''),
 ('title', (), 'Bootstrap title'),
 ('body', (), ''),
 ('div', (), ''),
 ('h1', (), 'Hello, world!'),
 ('span', (), 'I am a nested span.'),
 ('ul', (('class', 'list-group'),), ''),
 ('li', (('class', 'list-group-item'),), 'foo item number 0'),
 ('li', (('class', 'list-group-item'),), 'foo item number 1'),
 ('li', (('class', 'list-group-item'),), 'foo item number 2'),
 ('li', (('class', 'list-group-item'),), 'foo item number 3'),
 ('li', (('class', 'list-group-item'),), 'foo item number 4'),
 ('li', (('class', 'list-group-item'),), 'foo item number 5'),
 ('li', (('class', 'list-group-item'),), 'foo item number 6'),
 ('li', (('class', 'list-group-item'),), 'foo item number 7'),
 ('li', (('class', 'list-group-item'),), 'foo item number 8'),
 ('li', (('class', 'list-group-item'),), 'foo item number 9')]

Define some constants.

In [9]:
INNER_CONTENT, ATTRIBS, ATTRIBUTES, TAIL = (
    "inner_content",
    "attribs",
    "attributes",
    "tail",
)

Define functions to recursively walk the element tree and convert to nested dictionaries and lists.

In [10]:
def dictdata(node):
    res = {}
    res[node.tag] = []
    html_to_dict(node, res[node.tag])
    reply = {}
    reply[node.tag] = {
        INNER_CONTENT: res[node.tag],
        ATTRIBS: node.attrib,
        TAIL: node.tail,
    }
    return reply


def html_to_dict(node, res):
    rep = {}
    if len(node):
        for n in list(node):
            rep[node.tag] = []
            value = html_to_dict(n, rep[node.tag])
            if len(n):

                value = {
                    INNER_CONTENT: rep[node.tag],
                    ATTRIBUTES: n.attrib,
                    TAIL: n.tail,
                }
                res.append({n.tag: value})
            else:
                res.append(rep[node.tag][0])
    else:
        value = {}
        value = {INNER_CONTENT: node.text, ATTRIBUTES: node.attrib, TAIL: node.tail}
        res.append({node.tag: value})
    return None
In [11]:
data = dictdata(tree.getroottree().getroot())
In [12]:
data
Out[12]:
{'html': {'inner_content': [{'head': {'inner_content': [{<cyfunction Comment at 0x7f2c140317a0>: {'inner_content': ' Required meta tags ',
        'attributes': <lxml.etree._ImmutableMapping at 0x7f2c1401c780>,
        'tail': '\n    '}},
      {'meta': {'inner_content': None,
        'attributes': {'charset': 'utf-8'},
        'tail': '\n    '}},
      {'meta': {'inner_content': None,
        'attributes': {'name': 'viewport', 'content': 'width=device-width, initial-scale=1, shrink-to-fit=no'},
        'tail': '\n    '}},
      {<cyfunction Comment at 0x7f2c140317a0>: {'inner_content': ' Bootstrap CSS ',
        'attributes': <lxml.etree._ImmutableMapping at 0x7f2c1401c780>,
        'tail': '\n    '}},
      {'link': {'inner_content': None,
        'attributes': {'rel': 'stylesheet', 'href': 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css', 'integrity': 'sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T', 'crossorigin': 'anonymous'},
        'tail': '\n    '}},
      {<cyfunction Comment at 0x7f2c140317a0>: {'inner_content': ' Optional JavaScript ',
        'attributes': <lxml.etree._ImmutableMapping at 0x7f2c1401c780>,
        'tail': '\n    '}},
      {<cyfunction Comment at 0x7f2c140317a0>: {'inner_content': ' jQuery first, then Popper.js, then Bootstrap JS ',
        'attributes': <lxml.etree._ImmutableMapping at 0x7f2c1401c780>,
        'tail': '\n    '}},
      {'script': {'inner_content': None,
        'attributes': {'defer': 'defer', 'src': 'https://code.jquery.com/jquery-3.3.1.slim.min.js', 'integrity': 'sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo', 'crossorigin': 'anonymous'},
        'tail': '\n    '}},
      {'script': {'inner_content': None,
        'attributes': {'defer': 'defer', 'src': 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js', 'integrity': 'sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1', 'crossorigin': 'anonymous'},
        'tail': '\n    '}},
      {'script': {'inner_content': None,
        'attributes': {'defer': 'defer', 'src': 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js', 'integrity': 'sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM', 'crossorigin': 'anonymous'},
        'tail': '\n    '}},
      {'title': {'inner_content': 'Bootstrap title',
        'attributes': {},
        'tail': '\n  '}}],
     'attributes': {},
     'tail': '\n  '}},
   {'body': {'inner_content': [{'div': {'inner_content': [{'h1': {'inner_content': [{'span': {'inner_content': 'I am a nested span.',
              'attributes': {},
              'tail': None}}],
           'attributes': {},
           'tail': '\n        '}},
         {'ul': {'inner_content': [{'li': {'inner_content': 'foo item number 0',
              'attributes': {'class': 'list-group-item'},
              'tail': '\n'}},
            {'li': {'inner_content': 'foo item number 1',
              'attributes': {'class': 'list-group-item'},
              'tail': '\n'}},
            {'li': {'inner_content': 'foo item number 2',
              'attributes': {'class': 'list-group-item'},
              'tail': '\n'}},
            {'li': {'inner_content': 'foo item number 3',
              'attributes': {'class': 'list-group-item'},
              'tail': '\n'}},
            {'li': {'inner_content': 'foo item number 4',
              'attributes': {'class': 'list-group-item'},
              'tail': '\n'}},
            {'li': {'inner_content': 'foo item number 5',
              'attributes': {'class': 'list-group-item'},
              'tail': '\n'}},
            {'li': {'inner_content': 'foo item number 6',
              'attributes': {'class': 'list-group-item'},
              'tail': '\n'}},
            {'li': {'inner_content': 'foo item number 7',
              'attributes': {'class': 'list-group-item'},
              'tail': '\n'}},
            {'li': {'inner_content': 'foo item number 8',
              'attributes': {'class': 'list-group-item'},
              'tail': '\n'}},
            {'li': {'inner_content': 'foo item number 9',
              'attributes': {'class': 'list-group-item'},
              'tail': None}}],
           'attributes': {'class': 'list-group'},
           'tail': '\n    '}}],
        'attributes': {},
        'tail': '\n  '}}],
     'attributes': {},
     'tail': '\n'}}],
  'attribs': {'lang': 'en'},
  'tail': None}}

Define functions for getting all the "paths" to item leaves in the nested dictionary and for getting the leaf using the path.

See this solution to Access nested dictionary items via a list of keys? on Stack Overflow.

In [13]:
def paths_in_data(data, parent=()):
    """Calculate keys and/or indices in dict."""

    if not any(isinstance(data, type_) for type_ in (dict, list, tuple)):
        return (parent,)
    else:
        try:
            return reduce(
                op.add,
                (paths_in_data(v, op.add(parent, (k,))) for k, v in data.items()),
                (),
            )
        except AttributeError:
            return reduce(
                op.add,
                (paths_in_data(v, op.add(parent, (data.index(v),))) for v in data),
                (),
            )


def get_from(data, path):
    """Get a leaf from iterable of keys and/or indices.
    
    :data: Collection where nodes are either a dict or list.
    :path: Collection of keys and/or indices leading to a leaf.
    """
    return reduce(op.getitem, path, data)

Get the items to change.

In [14]:
WANTED_TAGS = ("link", "script")
paths_to_mutables = [
    item for item in paths_in_data(data) if any(tag in item for tag in WANTED_TAGS)
]

Group the paths by HTML element

In [15]:
TAG_INDEX = 5
mutables = it.groupby(paths_to_mutables, key=op.itemgetter(TAG_INDEX))
for key, group in mutables:
    for row in group:
        print(row)
('html', 'inner_content', 0, 'head', 'inner_content', 4, 'link', 'inner_content')
('html', 'inner_content', 0, 'head', 'inner_content', 4, 'link', 'attributes')
('html', 'inner_content', 0, 'head', 'inner_content', 4, 'link', 'tail')
('html', 'inner_content', 0, 'head', 'inner_content', 7, 'script', 'inner_content')
('html', 'inner_content', 0, 'head', 'inner_content', 7, 'script', 'attributes')
('html', 'inner_content', 0, 'head', 'inner_content', 7, 'script', 'tail')
('html', 'inner_content', 0, 'head', 'inner_content', 8, 'script', 'inner_content')
('html', 'inner_content', 0, 'head', 'inner_content', 8, 'script', 'attributes')
('html', 'inner_content', 0, 'head', 'inner_content', 8, 'script', 'tail')
('html', 'inner_content', 0, 'head', 'inner_content', 9, 'script', 'inner_content')
('html', 'inner_content', 0, 'head', 'inner_content', 9, 'script', 'attributes')
('html', 'inner_content', 0, 'head', 'inner_content', 9, 'script', 'tail')
In [16]:
items_to_edit = [
    [get_from(data, row) for row in group][1:]  # attributes and (inner_content or tail)
    for key, group in it.groupby(paths_to_mutables, key=op.itemgetter(5))
]
items_to_edit
Out[16]:
[[{'rel': 'stylesheet', 'href': 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css', 'integrity': 'sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T', 'crossorigin': 'anonymous'},
  '\n    '],
 [{'defer': 'defer', 'src': 'https://code.jquery.com/jquery-3.3.1.slim.min.js', 'integrity': 'sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo', 'crossorigin': 'anonymous'},
  '\n    '],
 [{'defer': 'defer', 'src': 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js', 'integrity': 'sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1', 'crossorigin': 'anonymous'},
  '\n    '],
 [{'defer': 'defer', 'src': 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js', 'integrity': 'sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM', 'crossorigin': 'anonymous'},
  '\n    ']]
In [17]:
INTEGRITY = "integrity"
link_keys = ("href", "rel", INTEGRITY, "crossorigin")
script_keys = ("defer", "src", *link_keys[link_keys.index(INTEGRITY):])
TAIL_DEFAULT = "\n    "
DEFER = "defer"

Bootswatch css breaks basic Boostrap view.

In [18]:
STYLESHEET = "stylesheet"
BOOTSWATCH_LINK_DATA = (
    [
        None,
        dict(
            zip(
                link_keys,
                (
                    "http://netdna.bootstrapcdn.com/bootswatch/4.3.1/cerulean/bootstrap.min.css",
                    STYLESHEET,
                    None,
                    None,
                ),
            )
        ),
        TAIL_DEFAULT,
    ],
)
MY_LINK_DATA = (
    None,
    dict(
        zip(
            link_keys,
            (
                "https://static.apps.selfip.com/bootstrap/4.3.1/css/boostrap.min.css",
                STYLESHEET,
                None,
                None,
            ),
        )
    ),
    TAIL_DEFAULT,
)
ALTERNATE_LINK_DATA = (
    None,
    dict(
        zip(
            link_keys,
            (
                "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css",
                STYLESHEET,
                None,
                None,
            ),
        )
    ),
    TAIL_DEFAULT,
)

LINK_DATA = (
    None,
    items_to_edit[0][0],
    TAIL_DEFAULT,
)
LINK_DATA = ALTERNATE_LINK_DATA
ALTERNATE_LINK_DATA, items_to_edit[0][0]
Out[18]:
((None,
  {'href': 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css',
   'rel': 'stylesheet',
   'integrity': None,
   'crossorigin': None},
  '\n    '),
 {'rel': 'stylesheet', 'href': 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css', 'integrity': 'sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T', 'crossorigin': 'anonymous'})
In [19]:
new_values = (
    ("link", LINK_DATA),
    *(
        ("script", [None, dict(zip(script_keys, values)), TAIL_DEFAULT])
        for values in (
            (
                DEFER,
                "https://code.jquery.com/jquery-3.3.1.slim.min.js",
                "sha256-3edrmyuQ0w65f8gfBsqowzjJe2iM6n0nKciPUp8y+7E=",
                "anonymous",
            ),
            (
                DEFER,
                "https://unpkg.com/popper.js@1.14.7/dist/umd/popper.min.js",
                None,
                None,
            ),
            (
                DEFER,
                "https://ajax.aspnetcdn.com/ajax/bootstrap/4.3.1/bootstrap.min.js",
                None,
                None,
            ),
        )
    ),
)
In [20]:
TAG_INDEX = 5
grouped = (
    tuple(group)
    for key, group in it.groupby(paths_to_mutables, key=op.itemgetter(TAG_INDEX))
)
TAG_INDEX_ = 6
values = tuple(
    (paths[0][TAG_INDEX_], [get_from(data, path) for path in paths])
    for paths in grouped
)
values
Out[20]:
(('link',
  [None,
   {'rel': 'stylesheet', 'href': 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css', 'integrity': 'sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T', 'crossorigin': 'anonymous'},
   '\n    ']),
 ('script',
  [None,
   {'defer': 'defer', 'src': 'https://code.jquery.com/jquery-3.3.1.slim.min.js', 'integrity': 'sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo', 'crossorigin': 'anonymous'},
   '\n    ']),
 ('script',
  [None,
   {'defer': 'defer', 'src': 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js', 'integrity': 'sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1', 'crossorigin': 'anonymous'},
   '\n    ']),
 ('script',
  [None,
   {'defer': 'defer', 'src': 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js', 'integrity': 'sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM', 'crossorigin': 'anonymous'},
   '\n    ']))
In [21]:
previous_parts = [
    (
        CT(
            **dict(
                zip(
                    ("tag", "tal_statements", INNER_CONTENT),
                    (tag, (TS(ATTRIBUTES, ATTRIBUTES),), value[2],),
                )
            )
        ).render(attributes=value[1])
    )
    for tag, value in values
]
previous_parts
Out[21]:
['<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">',
 '<script defer="defer" src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous">\n    </script>',
 '<script defer="defer" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous">\n    </script>',
 '<script defer="defer" src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous">\n    </script>']
In [22]:
new_parts = [
    (
        CT(
            **dict(
                zip(
                    ("tag", "tal_statements", INNER_CONTENT),
                    (tag, (TS(ATTRIBUTES, ATTRIBUTES),), value[2],),
                )
            )
        ).render(attributes=value[1])
    )
    for tag, value in new_values
]
new_parts
Out[22]:
['<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">',
 '<script defer="defer" src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha256-3edrmyuQ0w65f8gfBsqowzjJe2iM6n0nKciPUp8y+7E=" crossorigin="anonymous">\n    </script>',
 '<script defer="defer" src="https://unpkg.com/popper.js@1.14.7/dist/umd/popper.min.js">\n    </script>',
 '<script defer="defer" src="https://ajax.aspnetcdn.com/ajax/bootstrap/4.3.1/bootstrap.min.js">\n    </script>']

Get the lines from starter_html that need replacing

In [23]:
lines_to_replace = (
    (
        i,
        line
        if any(
            item.tag in WANTED_TAGS for item in tuple(element.iterdescendants())[-1:]
        )
        else None,
    )
    for i, line in enumerate(starter_html.splitlines())
    if (element := etree.fromstring(line, HTML_PARSER)) is not None
)
indices, _ = zip(*((i, _) for i, _ in lines_to_replace if _))
indices
Out[23]:
(7, 10, 11, 12)
In [24]:
new_parts_iter = iter(new_parts)
new_html = Join.LINES(
    line if i not in indices else next(new_parts_iter)
    for i, line in enumerate(starter_html.splitlines())
)
print(BeautifulSoup(new_html, "html.parser").prettify())
<!DOCTYPE doctype html>
<html lang="en">
 <head>
  <!-- Required meta tags -->
  <meta charset="utf-8"/>
  <meta content="width=device-width, initial-scale=1, shrink-to-fit=no" name="viewport"/>
  <!-- Bootstrap CSS -->
  <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/>
  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script crossorigin="anonymous" defer="defer" integrity="sha256-3edrmyuQ0w65f8gfBsqowzjJe2iM6n0nKciPUp8y+7E=" src="https://code.jquery.com/jquery-3.3.1.slim.min.js">
  </script>
  <script defer="defer" src="https://unpkg.com/popper.js@1.14.7/dist/umd/popper.min.js">
  </script>
  <script defer="defer" src="https://ajax.aspnetcdn.com/ajax/bootstrap/4.3.1/bootstrap.min.js">
  </script>
  <title>
   Bootstrap title
  </title>
 </head>
 <body>
  <div>
   <h1>
    Hello, world!
    <span>
     I am a nested span.
    </span>
   </h1>
   <ul class="list-group">
    <li class="list-group-item">
     foo item number 0
    </li>
    <li class="list-group-item">
     foo item number 1
    </li>
    <li class="list-group-item">
     foo item number 2
    </li>
    <li class="list-group-item">
     foo item number 3
    </li>
    <li class="list-group-item">
     foo item number 4
    </li>
    <li class="list-group-item">
     foo item number 5
    </li>
    <li class="list-group-item">
     foo item number 6
    </li>
    <li class="list-group-item">
     foo item number 7
    </li>
    <li class="list-group-item">
     foo item number 8
    </li>
    <li class="list-group-item">
     foo item number 9
    </li>
   </ul>
  </div>
 </body>
</html>

Verify that new_html displays Boostrap styling.

In [25]:
url = save_to_minio(new_html)
print(url)
https://minio.apps.selfip.com/mymedia/html/tmp8rtlpbmx.html

All values were programmatically replaced with the above code.

In [26]:
display(IFrame(src=url, width="auto", height=500))

What is SymPy?

Make a note about SymPy

SymPy is a Python library for symbolic mathematics. It aims to become a full-featured computer algebra system (CAS) while keeping the code as simple as possible in order to be comprehensible and easily extensible. SymPy is written entirely in Python.

Resources

In [5]:
from sympy import Symbol, lambdify
def Unit(x):
    if(x != 0):
        return 0
    else:
        return 1

x = Symbol('x')
x
Out[5]:
$\displaystyle x$

Maths formatted output!

In [6]:
fx = x**2 + Unit(x)
fx
Out[6]:
$\displaystyle x^{2}$
In [7]:
lam_f = lambdify(x, fx, modules=['sympy'])
lam_f
Out[7]:
<function _lambdifygenerated(x)>
In [8]:
print(lam_f(-1)) # prints 1
1

Stack Overflow Solution: Comprehension List and new File

Stack Overflow solution

In [69]:
from pathlib import Path
import tempfile
from datetime import datetime
import operator as op
In [70]:
_, filename = tempfile.mkstemp()
file_A = Path(filename)
_, filename = tempfile.mkstemp()
file_B = Path(filename)
In [71]:
contents_A = """Adam Brown 10/11/1999
Lauren Marie Smith 9/8/2001
Vincent Guth II 7/9/1980"""
fileA.write_text(contents_A)
Out[71]:
74

Without the assignment operator.

In [72]:
file_B.write_text("\n".join([
    " ".join(
        op.add(
            line.split()[:-1],
            [
                datetime.strptime(
                    line.split()[-1], "%m/%d/%Y",
                ).strftime("%-d/%-m/%Y",),
            ],
        )
    )
    for line in fileA.read_text().splitlines()
]))
Out[72]:
74

With the assignment operator.

In [73]:
file_B.write_text(
    "\n".join( # re-join the lines
        [
            " ".join( # re-join the words
                (
                    # Store the split line into words. Then slice all but last.
                    " ".join((words := line.split())[:-1]),
                    # Convert the last word to desired date format.
                    datetime.strptime(words[-1], "%m/%d/%Y",).strftime("%-d/%-m/%Y",),
                )
            )
            for line in fileA.read_text().splitlines()
        ]
    )
)
Out[73]:
74
In [74]:
print(file_B.read_text())
Adam Brown 11/10/1999
Lauren Marie Smith 8/9/2001
Vincent Guth II 9/7/1980

Trees Using Autovivification with Default Dict

Create trees using autovivification in Python with defaultdict.

Resources

In [1]:
from collections import defaultdict
import operator as op
from functools import reduce, partial
from pprint import pprint

from bs4 import BeautifulSoup
In [2]:
_pprint = partial(pprint, indent=4)
In [3]:
def tree():
    return defaultdict(tree)
In [4]:
def dicts(t):
    return {k: dicts(t[k]) for k in t}
In [5]:
def get(item, path):
    return reduce(op.getitem, path, item)
In [6]:
def add(t, path):
    for node in path:
        t = t[node]
In [7]:
taxonomy = tree()  # root
path = "Animalia>Chordata>Mammalia>Cetacea>Balaenopteridae>Balaenoptera>blue whale".split(
    ">"
)
path
Out[7]:
['Animalia',
 'Chordata',
 'Mammalia',
 'Cetacea',
 'Balaenopteridae',
 'Balaenoptera',
 'blue whale']
In [8]:
add(
    taxonomy, path,
)
None  # prevent output

❝Flat is better than nested.❞

A dict with keys that are the path to a node.

In [9]:
{
    path_ or ("root",): dicts(get(taxonomy, path_))
    for path_ in (tuple(path[:i]) for i in range(len(path)))
}  # order is outermost to innermost
Out[9]:
{('root',): {'Animalia': {'Chordata': {'Mammalia': {'Cetacea': {'Balaenopteridae': {'Balaenoptera': {'blue whale': {}}}}}}}},
 ('Animalia',): {'Chordata': {'Mammalia': {'Cetacea': {'Balaenopteridae': {'Balaenoptera': {'blue whale': {}}}}}}},
 ('Animalia',
  'Chordata'): {'Mammalia': {'Cetacea': {'Balaenopteridae': {'Balaenoptera': {'blue whale': {}}}}}},
 ('Animalia',
  'Chordata',
  'Mammalia'): {'Cetacea': {'Balaenopteridae': {'Balaenoptera': {'blue whale': {}}}}},
 ('Animalia',
  'Chordata',
  'Mammalia',
  'Cetacea'): {'Balaenopteridae': {'Balaenoptera': {'blue whale': {}}}},
 ('Animalia',
  'Chordata',
  'Mammalia',
  'Cetacea',
  'Balaenopteridae'): {'Balaenoptera': {'blue whale': {}}},
 ('Animalia',
  'Chordata',
  'Mammalia',
  'Cetacea',
  'Balaenopteridae',
  'Balaenoptera'): {'blue whale': {}}}
In [10]:
from chamelboots import ChameleonTemplate as CT
from chamelboots import TalStatement as TS
from chamelboots.constants import Join

ATTRIBUTE_CONTENT = (
    TS("attributes", "attrib"),
    TS("content", "structure content"),
)
In [11]:
ID = "query"
groups = (
    (("form", tuple({"method": "GET", "action": "#", "id": "search-form"}.items()),),),
    (("div", tuple({"class": "form-group",}.items()),),),
    (
        ("label", tuple({"class": "col-sm-2 col-form-label col-form-label-lg", "for": ID,}.items()),),
        (
            "input",
            tuple({
                "type": "text",
                "class": "form-control",
                "id": ID,
                "placeholder": "Enter search term",
            }.items()),
        ),
        ("button", tuple({"type": "submit", "class": "btn btn-primary"}.items()),),
        ("button", tuple({"type": "button", "class": "btn btn-secondary"}.items()),),
    ),
)
root = tree()
add(root, groups)
dom = dicts(root)
_pprint(dom)
{   (('form', (('method', 'GET'), ('action', '#'), ('id', 'search-form'))),): {   (('div', (('class', 'form-group'),)),): {   (('label', (('class', 'col-sm-2 col-form-label col-form-label-lg'), ('for', 'query'))), ('input', (('type', 'text'), ('class', 'form-control'), ('id', 'query'), ('placeholder', 'Enter search term'))), ('button', (('type', 'submit'), ('class', 'btn btn-primary'))), ('button', (('type', 'button'), ('class', 'btn btn-secondary')))): {   }}}}

Convert the dom into HTML.

In [12]:
groups = (
    (("form", (("method", "GET"), ("action", "#"), ("id", "search-form")),),),
    (("div", (("class", "form-group"),),),),
    (
        (
            "label",
            (("class", "col-sm-2 col-form-label col-form-label-lg",), ("for", ID,),),
        ),
        (
            "input",
            (
                ("type", "text"),
                ("class", "form-control"),
                ("id", ID),
                ("placeholder", "Enter search term"),
            ),
        ),
        ("button", (("type", "submit",), ("class", "btn btn-primary"),),),
        ("button", (("type", "submit",), ("class", "btn btn-secondary"),),),
    ),
)
root = tree()
add(root, groups)
dom = dicts(root)

start = len(groups) - 1
leaf = Join.LINES(
    CT(tag, ATTRIBUTE_CONTENT).render(attrib=dict(attrib), content="")
    for items in get(dom, groups[:start]).keys()
    for tag, attrib in items
)

for i in reversed(range(start)):
    leaf = Join.LINES(
        CT(tag, ATTRIBUTE_CONTENT).render(attrib=dict(attrib), content=leaf)
        for items in get(dom, groups[:i]).keys()
        for tag, attrib in items
    )
print(BeautifulSoup(leaf, "html.parser").prettify())
<form action="#" id="search-form" method="GET">
 <div class="form-group">
  <label class="col-sm-2 col-form-label col-form-label-lg" for="query">
  </label>
  <input class="form-control" id="query" placeholder="Enter search term" type="text"/>
  <button class="btn btn-primary" type="submit">
  </button>
  <button class="btn btn-secondary" type="submit">
  </button>
 </div>
</form>

Avoid using nested dicts.

In [13]:
groups = (
    (("form", (("method", "GET"), ("action", "#"), ("id", "search-form",)),),),
    (("div", (("class", "form-group"),),),),
    (
        (
            "label",
            (("class", "col-sm-2 col-form-label col-form-label-lg",), ("for", ID,),),
        ),
        (
            "input",
            (
                ("type", "text"),
                ("class", "form-control"),
                ("id", ID),
                ("placeholder", "Enter search term"),
            ),
        ),
        ("button", (("type", "submit",), ("class", "btn btn-primary"),),),
        ("button", (("type", "button",), ("class", "btn btn-secondary"),),),
    ),
)
groups_ = groups[::-1]  # reverse
leaf = Join.LINES(
    CT(tag, ATTRIBUTE_CONTENT).render(attrib=dict(attrib), content=content)
    for (tag, attrib), content in zip(groups_[0], ("Search", "", "search", "clear"))
)
for i in range(1, len(groups)):
    leaf = Join.LINES(
        CT(tag, ATTRIBUTE_CONTENT).render(attrib=dict(attrib), content=leaf)
        for tag, attrib in groups_[i]
    )
print(BeautifulSoup(leaf, "html.parser").prettify())
<form action="#" id="search-form" method="GET">
 <div class="form-group">
  <label class="col-sm-2 col-form-label col-form-label-lg" for="query">
   Search
  </label>
  <input class="form-control" id="query" placeholder="Enter search term" type="text"/>
  <button class="btn btn-primary" type="submit">
   search
  </button>
  <button class="btn btn-secondary" type="button">
   clear
  </button>
 </div>
</form>
In [14]:
from IPython.display import HTML
HTML(leaf)
Out[14]:
In [15]:
from chamelboots.constants import FAKE

string = CT(
    "button",
    (TS("repeat", "button buttons"), TS("attributes", "attrib")),
    inner_content="${button}",
).render(
    buttons=(FAKE.name() for _ in range(5)),
    attrib=dict((("type", "button",), ("class", "btn btn-success"),)),
)

display(HTML(string))
print(BeautifulSoup(string, "html.parser").prettify())
<button class="btn btn-success" type="button">
 John Howell
</button>
<button class="btn btn-success" type="button">
 Anthony Stevenson
</button>
<button class="btn btn-success" type="button">
 Zachary Williams
</button>
<button class="btn btn-success" type="button">
 Chelsea Wilson
</button>
<button class="btn btn-success" type="button">
 Scott Reyes
</button>

Create DOM Tree with chamelboots ChameleonTemplate

Create DOM Tree with strings and dicts.

In [6]:
from bs4 import BeautifulSoup
In [7]:
from chamelboots import ChameleonTemplate as CT
from chamelboots import TalStatement as TS
from chamelboots.constants import Join

ATTRIBUTE_CONTENT = (
    TS("attributes", "attrib"),
    TS("content", "structure content"),
)
In [9]:
ID = "query"
ATTRIB_CONTENTS = ATTRIBUTES, STRUCTURE_CONTENT = (
    TS("attributes", "attrib"),
    TS("content", "structure content"),
)
SEARCH_FORM = "searchForm"
In [56]:
groups = (
    (("form", {"method": "GET", "action": "#", "id": SEARCH_FORM},),),
    (("div", {"class": "form-group",}),),
    (
        ("label", {"class": "col-sm-2 col-form-label col-form-label-lg", "for": ID,},),
        (
            "input",
            {
                "type": "text",
                "class": "form-control",
                "id": ID,
                "placeholder": "Enter search term",
            },
        ),
        ("button", {"type": "submit", "class": "btn btn-primary"}),
        ("button", {"type": "button", "class": "btn btn-secondary"}),
    ),
)

chameleon_templates = [
    [(CT(tag, ATTRIB_CONTENTS), attrib,) for tag, attrib in group] for group in groups
]
<div class="form-group">
 <label class="col-sm-2 col-form-label col-form-label-lg" for="query">
 </label>
 <input class="form-control" id="query" placeholder="Enter search term" type="text"/>
 <button class="btn btn-primary" type="submit">
 </button>
 <button class="btn btn-secondary" type="button">
 </button>
</div>
==========
<form action="#" id="searchForm" method="GET">
 <div class="form-group">
 </div>
</form>
==========
<form action="#" id="searchForm" method="GET">
 <form action="#" id="searchForm" method="GET">
  <div class="form-group">
  </div>
 </form>
</form>
==========
In [11]:
query_form = Join.LINES(
    [
        CT(
            "form", (TS("attributes", "attrib"), TS("content", "structure content"),)
        ).render(
            attrib={"method": "GET", "action": "#", "id": SEARCH_FORM},
            content=CT(
                "div", (TS("attributes", "attrib"), TS("content", "structure content"),)
            ).render(
                attrib={"class": "form-group",},
                content=Join.LINES(
                    [
                        CT(
                            "label",
                            (TS("attributes", "attrib"),),
                            inner_content="Bootstrap Classes Search: Search the table for text.",
                        ).render(
                            attrib={
                                "class": "col-sm-2 col-form-label col-form-label-lg",
                                "for": ID,
                            }
                        ),
                        CT("input", (TS("attributes", "attrib"),),).render(
                            attrib={
                                "type": "text",
                                "class": "form-control",
                                "id": ID,
                                "placeholder": "Enter search term",
                            }
                        ),
                        CT(
                            "button",
                            (TS("attributes", "attrib"),),
                            inner_content="search",
                        ).render(attrib={"type": "submit", "class": "btn btn-primary"}),
                        CT(
                            "button",
                            (TS("attributes", "attrib"),),
                            inner_content="clear",
                        ).render(
                            attrib={"type": "button", "class": "btn btn-secondary"}
                        ),
                    ]
                ),
            ),
        ),
    ]
)

print(BeautifulSoup(query_form, 'html.parser').prettify())
<form action="#" id="searchForm" method="GET">
 <div class="form-group">
  <label class="col-sm-2 col-form-label col-form-label-lg" for="query">
   Bootstrap Classes Search: Search the table for text.
  </label>
  <input class="form-control" id="query" placeholder="Enter search term" type="text"/>
  <button class="btn btn-primary" type="submit">
   search
  </button>
  <button class="btn btn-secondary" type="button">
   clear
  </button>
 </div>
</form>

Create Table of all Bootstrap Classes

In [178]:
from IPython.display import display, HTML, Javascript
from chamelboots import ChameleonTemplate as CT
from chamelboots import TalStatement as TS
from chamelboots.constants import Join

When this notebook is running on a Jupyter notebook server, the HTML table is searchable.

It will not function when rendered as a blog post in Nikola.

In [2]:
ATTRIB_CONTENTS = ATTRIBUTES, STRUCTURE_CONTENT = (
    TS("attributes", "attrib"),
    TS("content", "structure content"),
)
SEARCH_FORM = "searchForm"
TOP = "top"
In [183]:
HTML(
    Join.LINES(
        (
            CT("h1", ATTRIB_CONTENTS).render(
                attrib={"class": "display-4"},
                content=CT(
                    "a", (ATTRIBUTES,), inner_content="Jump to Search Form"
                ).render(attrib=dict(href=f"#{SEARCH_FORM}", id=TOP)),
            ),
            CT("h1", ATTRIB_CONTENTS).render(
                attrib={"class": "display-4"},
                content=CT(
                    "a", (ATTRIBUTES,), inner_content="Jump to Functioning Search Form in HTML"
                ).render(attrib=dict(href=f"link://slug/searchable-table-of-bootstrap-classes")),
            ),
        )
    )
)
In [4]:
import urllib.request
import urllib.parse
from urllib.error import HTTPError
from enum import Enum
import itertools as it
from string import punctuation

from chamelboots.constants import global_safe, WhiteSpace, Join
from lxml import etree
import pandas as pd

from bs4 import BeautifulSoup

python-chamelboots

A python library I am working on:

Download the HTML containing a table with all the Bootstrap classes

In [5]:
URL = "https://bootstrapcreative.com/resources/bootstrap-4-css-classes-index/"
with urllib.request.urlopen(URL) as fh:
    HTML_STRING = etree.parse(fh, etree.HTMLParser())

Create a data frame with all the Boostrap info

In [6]:
doc = HTML_STRING.getroot()

tbody, _, _ = (e for e in doc.iter() if e.tag == "tbody")

data_ = (
    [
        str(cell.text).strip() or (cell.find("a") and cell.find("a"))
        for cell in row.iterchildren()
    ]
    for row in tbody.iterchildren()
    if row.tag == "tr"
)
data = [
    [
        cell.text.strip() if (not isinstance(cell, str) and cell is not None) else cell
        for cell in row
    ]
    for row in data_
]


class PPEnum(Enum):
    def __str__(self):
        return self.value


COLUMNS = ["class_name", "description", "category"]
Columns = PPEnum(
    "Colums", type=str, names=zip((item.upper() for item in COLUMNS), COLUMNS)
)
if global_safe(Columns.__members__):
    globals().update(Columns.__members__)

df = (
    pd.DataFrame([rows[:1] + rows[2:] for rows in data], columns=Columns)
    .sort_values(by=CATEGORY, axis=0)
    .reset_index(drop=True)
)
In [7]:
bootstrap_url = "https://getbootstrap.com/docs/4.3/components/forms/"
tokens_ = urllib.parse.urlsplit(bootstrap_url)
tokens_
PREFIX_TOKENS = tokens_[:2]
SUFFIX_TOKENS = tokens_[3:]
PREFIX_TOKENS, SUFFIX_TOKENS
Out[7]:
(('https', 'getbootstrap.com'), ('', ''))
In [8]:
def verify_url(request):
    # Open the URL url, which can be either a string or a Request object.
    try:
        with urllib.request.urlopen(request) as fh:
            fh.getcode()
            return request.get_full_url()
    except HTTPError:
        return None

Get words from classes

In [9]:
translation = str.maketrans(dict(zip(punctuation, it.cycle(("",)))))
CLASS_WORDS = {item.split('-')[0].translate(translation).title() for item in df[CLASS_NAME]}
list(CLASS_WORDS)[0:10]
Out[9]:
['Pre',
 'Progress',
 'Was',
 'Blockquote',
 'Badge',
 'Display',
 'Flex',
 'Modal',
 'Nav',
 'Input']

Make a guess at the URLs that could lead to documentation

In [10]:
categories_ = set(df[CATEGORY])
categories = [
    *categories_,
    *[item.strip("s") for item in categories_],
    *CLASS_WORDS,
]
In [11]:
UTILIT = "Utilit"
COMPONENTS = "components"
PATHPREFIX = "/docs/4.3/"
UTILITIES = "utilities"
PATH_PARTS = (UTILITIES, COMPONENTS)
[category for category in categories if UTILIT in category]
Out[11]:
['Utility', 'Utilities', 'Utility', 'Utilitie']
In [12]:
def transform(category):
    return "-".join(category.lower().split(" "))

Walrus operator opportunity!

Use "HEAD" method to try to find valid links to put into table.

In [13]:
def get_valid_urls(path_part):
    return sorted(
        {
            (category, result)
            for category in categories
            if (
                result := verify_url(# verify with function and capture with the walrus.
                    urllib.request.Request( # create a request object
                        urllib.parse.urlunsplit( # create a URL
                            (
                                *PREFIX_TOKENS,
                                f"{PATHPREFIX}{path_part}/{transform(category)}/",
                                *SUFFIX_TOKENS,
                            )
                        ),
                        method="HEAD",
                        headers={"User-Agent": "Mozilla/5.0"},
                    )
                )
            )
            is not None
        }
    )
In [14]:
valid_urls = list(
    it.chain.from_iterable(get_valid_urls(path_part) for path_part in PATH_PARTS)
)
In [15]:
[item for item in valid_urls if UTILITIES in item[-1]]
Out[15]:
[('Clearfix', 'https://getbootstrap.com/docs/4.3/utilities/clearfix/'),
 ('Display', 'https://getbootstrap.com/docs/4.3/utilities/display/'),
 ('Embed', 'https://getbootstrap.com/docs/4.3/utilities/embed/'),
 ('Flex', 'https://getbootstrap.com/docs/4.3/utilities/flex/'),
 ('Float', 'https://getbootstrap.com/docs/4.3/utilities/float/'),
 ('Position', 'https://getbootstrap.com/docs/4.3/utilities/position/'),
 ('Text', 'https://getbootstrap.com/docs/4.3/utilities/text/')]
In [16]:
display(HTML(df.head().to_html()))
class_name description category
0 .alert-link When you add links inside alert this class matches the font color to the parent alert class. Alerts
1 .alert-heading This class is added to headings inside alerts. It applies color:inherit so the colors match. Alerts
2 .fade To have your alerts use animation when closing, make sure they have the .fade and .in classes already applied to them. Alerts
3 .alert The .alert class adds base styling with padding and margin. Alerts
4 .alert-* Change the color of the alert to provide user feedback. (primary, secondary, success, danger, warning, info, light, dark) Alerts
In [17]:
valid_urls
Out[17]:
[('Clearfix', 'https://getbootstrap.com/docs/4.3/utilities/clearfix/'),
 ('Display', 'https://getbootstrap.com/docs/4.3/utilities/display/'),
 ('Embed', 'https://getbootstrap.com/docs/4.3/utilities/embed/'),
 ('Flex', 'https://getbootstrap.com/docs/4.3/utilities/flex/'),
 ('Float', 'https://getbootstrap.com/docs/4.3/utilities/float/'),
 ('Position', 'https://getbootstrap.com/docs/4.3/utilities/position/'),
 ('Text', 'https://getbootstrap.com/docs/4.3/utilities/text/'),
 ('Alerts', 'https://getbootstrap.com/docs/4.3/components/alerts/'),
 ('Badge', 'https://getbootstrap.com/docs/4.3/components/badge/'),
 ('Breadcrumb', 'https://getbootstrap.com/docs/4.3/components/breadcrumb/'),
 ('Button Group',
  'https://getbootstrap.com/docs/4.3/components/button-group/'),
 ('Button group',
  'https://getbootstrap.com/docs/4.3/components/button-group/'),
 ('Buttons', 'https://getbootstrap.com/docs/4.3/components/buttons/'),
 ('Card', 'https://getbootstrap.com/docs/4.3/components/card/'),
 ('Carousel', 'https://getbootstrap.com/docs/4.3/components/carousel/'),
 ('Collapse', 'https://getbootstrap.com/docs/4.3/components/collapse/'),
 ('Dropdowns', 'https://getbootstrap.com/docs/4.3/components/dropdowns/'),
 ('Forms', 'https://getbootstrap.com/docs/4.3/components/forms/'),
 ('Input Group', 'https://getbootstrap.com/docs/4.3/components/input-group/'),
 ('Input group', 'https://getbootstrap.com/docs/4.3/components/input-group/'),
 ('Jumbotron', 'https://getbootstrap.com/docs/4.3/components/jumbotron/'),
 ('List Group', 'https://getbootstrap.com/docs/4.3/components/list-group/'),
 ('List group', 'https://getbootstrap.com/docs/4.3/components/list-group/'),
 ('Media Object',
  'https://getbootstrap.com/docs/4.3/components/media-object/'),
 ('Media object',
  'https://getbootstrap.com/docs/4.3/components/media-object/'),
 ('Modal', 'https://getbootstrap.com/docs/4.3/components/modal/'),
 ('Navbar', 'https://getbootstrap.com/docs/4.3/components/navbar/'),
 ('Navs', 'https://getbootstrap.com/docs/4.3/components/navs/'),
 ('Pagination', 'https://getbootstrap.com/docs/4.3/components/pagination/'),
 ('Progress', 'https://getbootstrap.com/docs/4.3/components/progress/')]
In [18]:
series = pd.Series([None for _ in range(len(df))])

for category, url in valid_urls:
    for i, item in enumerate(df.itertuples()):
        lower_category = category.lower()
        if any(
            (
                lower_category in item.category.lower(),
                lower_category in item.class_name.lower(),
            )
        ):
            # print(i, url, item.category)
            series[i] = CT(
                "a", (TS("attributes", "attrib"),), inner_content="bootstrap docs"
            ).render(attrib=dict(href=url, target="_blank"))
df["URLs"] = series

Create Boostrap form with chamelboots.ChameleonTemplate

A pattern is developing.

This doesn't (yet) look any more useful than typing HTML by hand.

I see some patterns developing and will further refactor python-chamelboots.

In [19]:
ID = "query"
query_form = Join.LINES(
    [
        CT(
            "form", (TS("attributes", "attrib"), TS("content", "structure content"),)
        ).render(
            attrib={"method": "GET", "action": "#", "id": SEARCH_FORM},
            content=CT(
                "div", (TS("attributes", "attrib"), TS("content", "structure content"),)
            ).render(
                attrib={"class": "form-group",},
                content=Join.LINES(
                    [
                        CT(
                            "label",
                            (TS("attributes", "attrib"),),
                            inner_content="Bootstrap Classes Search: Search the table for text.",
                        ).render(
                            attrib={
                                "class": "col-sm-2 col-form-label col-form-label-lg",
                                "for": ID,
                            }
                        ),
                        CT("input", (TS("attributes", "attrib"),),).render(
                            attrib={
                                "type": "text",
                                "class": "form-control",
                                "id": ID,
                                "placeholder": "Enter search term",
                            }
                        ),
                        CT(
                            "button",
                            (TS("attributes", "attrib"),),
                            inner_content="search",
                        ).render(attrib={"type": "submit", "class": "btn btn-primary"}),
                        CT(
                            "button",
                            (TS("attributes", "attrib"),),
                            inner_content="clear",
                        ).render(
                            attrib={"type": "button", "class": "btn btn-secondary"}
                        ),
                    ]
                ),
            ),
        ),
    ]
)

HTML produced using chamelboots Chameleon Template

In [20]:
print(BeautifulSoup(query_form, "html.parser").prettify())
<form action="#" id="searchForm" method="GET">
 <div class="form-group">
  <label class="col-sm-2 col-form-label col-form-label-lg" for="query">
   Bootstrap Classes Search: Search the table for text.
  </label>
  <input class="form-control" id="query" placeholder="Enter search term" type="text"/>
  <button class="btn btn-primary" type="submit">
   search
  </button>
  <button class="btn btn-secondary" type="button">
   clear
  </button>
 </div>
</form>
In [24]:
table_html = df.to_html(escape=False)
In [25]:
for item in query_form, table_html:
    display(HTML(item))
class_name description category URLs
0 .alert-link When you add links inside alert this class matches the font color to the parent alert class. Alerts bootstrap docs
1 .alert-heading This class is added to headings inside alerts. It applies color:inherit so the colors match. Alerts bootstrap docs
2 .fade To have your alerts use animation when closing, make sure they have the .fade and .in classes already applied to them. Alerts bootstrap docs
3 .alert The .alert class adds base styling with padding and margin. Alerts bootstrap docs
4 .alert-* Change the color of the alert to provide user feedback. (primary, secondary, success, danger, warning, info, light, dark) Alerts bootstrap docs
5 .alert-dismissible Similar to a well it is a box with a border and padding. This class and a child element with a .close class lets the user close the alert. Alerts bootstrap docs
6 .badge-* Used for labels and counters in applications Badge bootstrap docs
7 .blockquote Add to blockquote elements to apply the proper spacing and bottom margin to seperate it from other text. Blockquotes None
8 .blockquote-footer Wrapping class for citation text underneath a blockquote. Used to lighten the text color. Blockquotes None
9 .breadcrumb Indicate the current page's location within a navigational hierarchy. Breadcrumbs bootstrap docs
10 .btn-group-toggle This class replaces an input checkbox with a custom style that is toggable on click Button Group bootstrap docs
11 .btn-group-lg Increases the default button group size Button Group bootstrap docs
12 .btn-group-sm Decreases the default button group size Button Group bootstrap docs
13 .btn-group Smushes multiple buttons together to make a pill shape. Each button is separated by a vertical line. Button groups bootstrap docs
14 .btn-group-vertical Make a set of buttons appear vertically stacked rather than horizontally. Button groups bootstrap docs
15 .btn-toolbar Used to create a row of buttons similar pagination row Button groups bootstrap docs
16 .btn-lg Increases the default button size Buttons bootstrap docs
17 .btn-outline-* Transparent background with colored text and border (danger|info|primary|secondary|success|warning) Buttons bootstrap docs
18 .btn-success Defaults to a green background button with dark border. Buttons bootstrap docs
19 .btn-warning Defaults to a yellow background button with dark border. Buttons bootstrap docs
20 .disabled Add this class to anchor tags to disable the click functionality but still have them visible. Buttons bootstrap docs
21 .btn-info Button for information on a topic like terms and conditions. Default is light blue. Buttons bootstrap docs
22 .btn-sm Fancy larger or smaller buttons? Add .btn-lg, .btn-sm, or .btn-xs for additional sizes. Buttons bootstrap docs
23 .btn This class sets the spacing and size of the button. Buttons bootstrap docs
24 .btn-primary Use for the primary action in a set. Buttons bootstrap docs
25 .btn-block By default buttons are inline this class makes it block to span the full width of its parent. Buttons bootstrap docs
26 .btn-lg Fancy larger or smaller buttons? Add .btn-lg, .btn-sm, or .btn-xs for additional sizes. Buttons bootstrap docs
27 .btn-link Keeps the spacing set with the .btn class but removes the outer border. Buttons bootstrap docs
28 .btn-danger Defaults to a red background button Buttons bootstrap docs
29 .btn-outline-* A button variation to have outlined buttons instead of a solid background. Buttons bootstrap docs
30 .card-header-tabs Class added in combination with .nav-pills to add tab navigation to a card header Cards bootstrap docs
31 .card-group The parent wrapping class around a group of cards. Groups are similar to decks but they have no margin between each card. Cards bootstrap docs
32 .card-img Add this class to the image you would like to have as a card background image. Used with creating cards with image overlays. Cards bootstrap docs
33 .card-* First add .card-inverse and then add one of the contextual background colors (danger|info|primary|secondary|success|warning) Cards bootstrap docs
34 .card The class added to the div that wraps each individual card Cards bootstrap docs
35 .card-body This class is added to the first child div inside the div.card parent Cards bootstrap docs
36 .card-columns The .card-columns class is added to the wrapping div of of masonry-like collection of cards Cards bootstrap docs
37 .card-deck Similar to columns Bootstrap card decks are equal height and width Cards bootstrap docs
38 .card-footer Wrap class for a card footer content area Cards bootstrap docs
39 .card-text This class wraps the container around card text Cards bootstrap docs
40 .card-title The class added to titles inside cards. It applies the proper spacing. Cards bootstrap docs
41 .card-img-overlay Wrapper class used to create a card that has a background image overlay Cards bootstrap docs
42 .list-group-flush When adding a list group to a card add this class to the list group to remove the border. Otherwise you will have a double border. Cards bootstrap docs
43 .card-img-bottom Similar to a card footer you can cap the bottom of a card with an image Cards bootstrap docs
44 .card-header Wrap class for a card header content area Cards bootstrap docs
45 .card-subtitle Class added to card subtitles that adjusts the default heading styles Cards bootstrap docs
46 .card-header-pills Class added in combination with .nav-pills to add pill navigation to a card header Cards bootstrap docs
47 .card-link Adds spacing around links added inside cards Cards bootstrap docs
48 .card-inverse Inverts the default colors to use light text on a dark background color Cards bootstrap docs
49 .card-img-top Similar to a card header you can cap the top of a card with an image Cards bootstrap docs
50 .carousel-item The wrapper class applied to each individual carousel item Carousel bootstrap docs
51 .carousel-indicators parent classed added to an ordered list for the little circles showing what slide you are on Carousel bootstrap docs
52 .carousel-inner The div wrapper that contains the carousel slide items Carousel bootstrap docs
53 .carousel parent carousel class making it position relative Carousel bootstrap docs
54 .carousel-caption Caption for each slide item Carousel bootstrap docs
55 .carousel-control-* When you have an image carousel with pagination you will use this class on the previous and next anchor links. Carousel bootstrap docs
56 .next Used in the carousel control to identity the next control Carousel bootstrap docs
57 .pre-scrollable You may optionally add the .pre-scrollable class, which will set a max-height of 350px and provide a y-axis scrollbar. Code None
58 .collapsing applied during transitions on hide and show component Collapse bootstrap docs
59 .collapse hides content on hide and show component Collapse bootstrap docs
60 .text-* Changes text color to a contextual color or grayscale value Color bootstrap docs
61 .figure-caption Added to a figure figcaption element to apply font styling Content None
62 .figure-img Classed added to images inside a figure to apply some margin Content None
63 .dropdown-toggle This class is added to the button that will have the toggle action applied that will hide and show the dropdown menu Dropdown None
64 .dropdown-header Used to add headers inside the dropdown menu Dropdown None
65 .dropdown-menu Adds the default styles for the dropdown menu container Dropdown None
66 .dropdown-divider Class adds a horizontal line between dropdown link items Dropdowns bootstrap docs
67 .dropdown-item This class is added to each link item shown in a dropdown menu Dropdowns bootstrap docs
68 .dropdown-toggle-split Class added to the notched dropdown navigation. Great for providing additional actions but still having a primary action. Dropdowns bootstrap docs
69 .dropdown This class gives you the ability to add a dropdown to navbar, tabs, and pills so you can display a dropdown of additional navigation. Dropdowns bootstrap docs
70 .dropleft Dropdown menu that opens left of the button Dropdowns bootstrap docs
71 .dropright Dropdown menu that opens right of the button Dropdowns bootstrap docs
72 .dropup Displays the dropdown menu above the button instead of below. Dropdowns bootstrap docs
73 .embed-responsive-4by3 Div wrapper class to make child iframe responsive Embed bootstrap docs
74 .embed-responsive-16by9 Div wrapper class to make child iframe responsive Embed bootstrap docs
75 .embed-responsive The default responsive iframe embed styles Embed bootstrap docs
76 .input-group-text This class adds the background color and text styles to the text inside an input group Forms bootstrap docs
77 .has-* The .has-(success, warning, danger) is added to the parent form element container to apply visual feedback to the user on form validation. Forms bootstrap docs
78 .form-text This class is used for help text alongside form elements. You can add .text-muted to make the text lighter in color Forms bootstrap docs
79 .form-inline Use this class to have a series of labels and form elements on a single horizontal row Forms bootstrap docs
80 .form-control-* Apply this class to form elements to increase or decrease its size relative to the default sizing .form-control-(lg, sm) Forms bootstrap docs
81 .form-control-file The class added to input type="file" to apply font and spacing Forms bootstrap docs
82 .form-check-label This class is added to checkbox and radio button labels Forms bootstrap docs
83 .form-check-input This class is added to the input tag for checkboxes and radio buttons. Adds styles for positioning and margins. Forms bootstrap docs
84 .form-check-inline Class used for a horizontal group of checkmarks or radio buttons Forms bootstrap docs
85 .form-check The parent class of form checkboxes Forms bootstrap docs
86 .custom-switch Creates a custom form element that looks like a toggle switch found on touch devices. Forms bootstrap docs
87 .custom-select-lg Increases the relative size of a custom form select Forms bootstrap docs
88 .col-form-label-sm Decreases the font size and spacing of a form label Forms bootstrap docs
89 .custom-select Class added to a select tag to create a custom select menu Forms bootstrap docs
90 .form-group A div wrapper class that goes around a form input and label Forms bootstrap docs
91 .form-control Class added input, textarea, and select to make them 100% and responsive Forms bootstrap docs
92 .form-control-plaintext Use the Forms bootstrap docs
93 .form-row Works similar to a grid Forms bootstrap docs
94 .custom-select-sm Decreases the font size and padding on a custom select Forms bootstrap docs
95 .is-* If you do server side form validation you can use this class to set feedback colors on inputs or error message. Forms bootstrap docs
96 .was-validated This class is set by Bootstrap's javascript to apply sub class validation styles to the form inputs. Forms bootstrap docs
97 .col-form-label Class added to form labels to apply consistent padding and margins Forms bootstrap docs
98 .col-form-label-lg Increases the font size and spacing of a form label Forms bootstrap docs
99 .invalid-feedback This class can be added with server side form validation to add a feedback message to an invalid field Forms bootstrap docs
100 .custom-file-control Similar to the custom-control-indicator class this class is added to a div to build a custom file input using CSS :before and :after. Forms bootstrap docs
101 .custom-control Used on all custom form inputs and adds base styles like padding and display:inline Forms bootstrap docs
102 .custom-control-inline Custom form checkboxes are set to be display:block. Use this class to make the checkbox inline Forms bootstrap docs
103 .custom-control-input This class is added to the default input that is going to be replaced. It adds the following: position: absolute; z-index: -1; opacity: 0; Forms bootstrap docs
104 .custom-radio This class is added to the parent label tag along with .custom-control class to specify what kind of custom input it will be Forms bootstrap docs
105 .custom-file-label When creating a custom form file browser, this class replaces the default file browser with custom elements using :after Forms bootstrap docs
106 .custom-file-input This class is added to the default input type="file" and hides it using CSS Forms bootstrap docs
107 .custom-control-label When creating a custom form checkbox, this class replaces the default checkbox with custom elements using :before and :after Forms bootstrap docs
108 .custom-file Class added to a label of grouped elements to create a custom file upload input Forms bootstrap docs
109 .custom-checkbox Parent class that converts a default form checkbox into a custom HTML/CSS checkbox Forms bootstrap docs
110 .col Flexbox items are automatically equal width so this class is used when you want your columns to be equal width and then go 100% on the xs breakpoint. Grid None
111 .order-*-* This class is used to control how the elements are ordered on the page regardless of their order in the source code. So you can rearrange your layout as needed. Grid None
112 .no-gutters Removes the negative margin on the Grid None
113 .col-* This class is used for grid columns to determin the column width and the breakpoint you would like it to be active. The classes work from the breakpoint you set and everything larger. Grid None
114 .offset-*-* Used to offset a grid column from its original position Grid System None
115 .row used a parent wrapper of any vertical columns Grid system None
116 .col-xl-* Set column width for anything greater than 1200px. Specify the column span by adding 1-12 at the end Grid system None
117 .col-*-* span 1-12 column. Extra small devices Phones (\n < 768px), Small devices Tablets (≥768px), Medium devices Desktops (≥992px), Large devices Desktops (≥1200px). Column device Column numeric values can be 1-12. Grid system None
118 .container-fluid Spans the full width of the screen Grid system None
119 .col-*-pull-* Easily change the order of our built-in grid columns with .col-*-push-* and .col-*-pull-* modifier classes. Pull numeric values can be 0-12. Grid system None
120 .container Fixed width container with widths determined by screen sites. Equal margin on the left and right. Grid system None
121 .col-*-push-* Easily change the order of our built-in grid columns with .col-*-push-* and .col-*-pull-* modifier classes. Push numeric values can be 0-12. Grid system None
122 .bg-success Similar to the contextual text color classes, easily set the background of an element to any contextual class. Anchor components will darken on hover, just like the text classes. Helper Classes None
123 .invisible Make something invisible Helper Classes None
124 .close Use the generic close icon for dismissing content like modals and alerts. Helper Classes None
125 .bg-primary Similar to the contextual text color classes, easily set the background of an element to any contextual class. Anchor components will darken on hover, just like the text classes. Helper Classes None
126 .bg-warning Similar to the contextual text color classes, easily set the background of an element to any contextual class. Anchor components will darken on hover, just like the text classes. Helper Classes None
127 .bg-danger Similar to the contextual text color classes, easily set the background of an element to any contextual class. Anchor components will darken on hover, just like the text classes. Helper Classes None
128 .sr-only Hide an element to all devices except screen readers with .sr-only. Helper Classes None
129 .bg-info Similar to the contextual text color classes, easily set the background of an element to any contextual class. Anchor components will darken on hover, just like the text classes. Helper Classes None
130 .rounded-* The .img-rounded class was renamed to this and is primarily used with images. However, the class just adds a border radius so you could use this on other elements that you would like a radius applied. You can also add Images None
131 .img-thumbnail Adds rounded corners and an inset border to an image Images None
132 .img-fluid This class is applied to images you would like to be responsive or fluid width across various screen sizes. This was .img-responsive in v3 Images None
133 .input-group-* This class lets you extend form controls by adding text or buttons to the left or right of the input. .input-group-(addon|btn) Input Group bootstrap docs
134 .input-group-prepend This class adds margin-right: -1px; to the input set to right of the group to compensate for the 1px border Input Group bootstrap docs
135 .input-group-append This class adds margin-left: -1px; to the input set to left of the group to compensate for the 1px border Input Group bootstrap docs
136 .input-group Wrapper class used to enhance an input and label group by adding a button in front or behind as help text Input groups bootstrap docs
137 .jumbotron-fluid A default jumbotron is not full width but adding this class removes the rounded corners and makes it extend to 100% of its parent Jumbotron bootstrap docs
138 .jumbotron A content section that is used to showcase important content. Commonly used on home pages and category pages. Jumbotron bootstrap docs
139 .list-group-item-action Add this class to each anchor in a list-group to remove the default anchor text color List Group bootstrap docs
140 .list-group Wrapper ul class that contains li with borders List group bootstrap docs
141 .list-group-horizontal The list group items are positioned horizontal intead of vertically. Be careful of long lists because they can break the layout. List group bootstrap docs
142 .list-group-item-* Change color of list group item by adding one of the following: default, warning, info, danger, primary List group bootstrap docs
143 .list-group-item-text Class added to an anchor or p for a .list-group-item text under a heading List group bootstrap docs
144 .list-group-item Class added to each li in a list-group List group bootstrap docs
145 .media-middle Add this class the div wrapping the media object image to center it vertically Media Object bootstrap docs
146 .media-left Add this class the div wrapping the media object image to align it to the left Media Object bootstrap docs
147 .media-bottom Add this class the div wrapping the media object image to align it to the bottom Media Object bootstrap docs
148 .media-right Add this class the div wrapping the media object image to align it to the right Media Object bootstrap docs
149 .media-body The class added for the media description copy block Media object bootstrap docs
150 .media Media components are image heading and description text items. Blog comments, portfolio projects, album covers, etc. Media object bootstrap docs
151 .modal-lg Makes a modal wider Modal bootstrap docs
152 .modal-dialog-centered Vertically and horizontally centers a modal dialog Modal bootstrap docs
153 .modal-header The header section of the modal that contains the title and close button Modal bootstrap docs
154 .modal-dialog The secondary wrapper class of the entire modal content Modal bootstrap docs
155 .modal-open Javascript adds this class to the body tag to prevent scrolling with the modal is open Modal bootstrap docs
156 .modal-sm Makes the modal not as wide Modal bootstrap docs
157 .modal The parent wrapper class of modal content Modal bootstrap docs
158 .modal-title The title of the modal Modal bootstrap docs
159 .modal-backdrop Added by the modal javascript to make the area around the modal clickable to hide the modal Modal bootstrap docs
160 .modal-body The modal body content class : Header - Body - Footer Modal bootstrap docs
161 .modal-footer The footer of the modal that contains action buttons or help text Modal bootstrap docs
162 .modal-content modal-content contains modal-body, modal-header, and modal-footer Modal bootstrap docs
163 .navbar-light Add this class to your navbar if you would like it to have a light background and dark text Navbar bootstrap docs
164 .navbar-toggler The infamous cheeseburger icon to signify a navigation menu on mobile Navbar bootstrap docs
165 .navbar-brand Most navbars contain a logo or brand. This class is added to the anchor Navbar bootstrap docs
166 .navbar-toggler-icon The cheeseburger navigation icon is set using an svg background image of three horizontal lines Navbar bootstrap docs
167 .navbar-text Vertically centers text inside a navbar Navbar bootstrap docs
168 .navbar-expand-* Since the navbar is displayed collapse on mobile first, this class specifies what breakpoint you want the navbar to not be collapsed Navbar bootstrap docs
169 .navbar-collapse The nav links that are collapsed and shown when toggled on mobile widths. Navbar bootstrap docs
170 .navbar-nav The wrapper class of the navigation elements excluding the brand Navbar bootstrap docs
171 .navbar Navigation header class Navbar bootstrap docs
172 .nav-justified Makes all nav items equal width and use all available horizontal space. Navs bootstrap docs
173 .nav-fill Makes all nav items use all available horizontal space. Nav items are different widths baded on their content. Navs bootstrap docs
174 .nav nav base class added all types of navigation: tabs, pills, justified, disabled links Navs bootstrap docs
175 .nav-item If your nav uses a list add this class to each list item for the proper spacing Navs bootstrap docs
176 .nav-pills Use this class along with .nav to make each nav link into a button Navs bootstrap docs
177 .nav-link Each anchor link inside your nav is given this class in order to have the proper styling Navs bootstrap docs
178 None Animates the slide transition with a crossfade instead of a slide None None
179 None Changes the default styling of a form input range None None
180 None Removes the interactivity from a dropdown so it does not appear clickable None None
181 None Changes the display of elements when you print the document. None None
182 None Add to all sibling elements you would like to force into equal widths and fill all available horizontal space. None None
183 None Forces an element to grow or shrink to use more or less of the space available None None
184 None Adds a black CSS box shadow to an element. None None
185 .pagination The wrapper class that contains all of the page navigation Pagination bootstrap docs
186 .pagination-lg Increases the font size and spacing of a pagination nav Pagination bootstrap docs
187 .page-item This class is added to each li inside the ul.pagination and floats the li's' Pagination bootstrap docs
188 .pagination-sm Decreases the font size and spacing of a pagination nav Pagination bootstrap docs
189 .page-link This class is added to each anchor link containing the numbers Pagination bootstrap docs
190 .progress-bar-animated When this class is added to a progressbar the progress will be animated using css3 animations Progress bootstrap docs
191 .progress-bar-striped Changes progress to a striped version Progress bars bootstrap docs
192 .progress The parent class wrapper of a progress bar Progress bars bootstrap docs
193 .progress-bar The class applied to the progress bar graphic that moves Progress bars bootstrap docs
194 .nav-tabs Class added to enable Bootstrap tabs Tab None
195 .tab-pane Class added to the div that will act as a tab content area Tab None
196 .table-responsive-* Makes a table responsive by cropping a wide table and makes it scrollable horizontally. .table-responsive or .table-responsive-(lg, md, sm, xl) Tables None
197 .table-bordered Adds borders to a table and its cells Tables None
198 .table-sm Removes some padding from the table cells Tables None
199 .table Adding this class to a HTML table applies the Bootstrap styles Tables None
200 .info Tables contextual class to change row color" target="_blank" href="https://getbootstrap.com/docs/4.3/content/tables/#contextual-classes Tables None
201 .table-* Adds a 1px stroke around the rows, columns, and table outline. You can also remove the borders entirely. Tables None
202 .active Tables contextual class to change row color Tables None
203 .table-striped Adds a light background color to every other table row for a striped effect Tables None
204 .table-reflow The table header becomes the first column of the table to the left Tables None
205 .thead-dark The default table head styling of light background and dark text Tables None
206 .thead-light Inverts the table head to have a dark background and light text Tables None
207 .table-hover Adds a background color when you hover a table row Tables None
208 .table-* Contextual classes of different color styles to provide user feedback (active|danger|info|primary|secondary|success|warning) Tables None
209 .success Tables contextual class to change row color Tables None
210 .table-sm Removes vertical padding between table rows so it does not take as much vertical space. Good for tables with a lot of rows. Tables None
211 .tooltip-inner The wrapper class of tooltip text. This is generated by the Bootstrap javascript Tooltip None
212 .tooltip This class is used by the tooltip javascript as the wrapper of the toolitp Tooltip None
213 .initialism Add .initialism to an abbreviation for a slightly smaller font-size. Typography None
214 .lead Increase the font size and line height of a paragraph. Good to use on the first paragraph of an article to improve readability. Typography None
215 .text-capitalize Capitalize the text or title case Typography bootstrap docs
216 .display-* This set of classes increases the font size of headings in 4 stages. These classes are used for headings outside of the main content of the page like jumbotrons and page headers. Append (1-4) to the end to adjust size. Typography bootstrap docs
217 .text-justify Full justifys the text Typography bootstrap docs
218 .text-lowercase Changes text to lowercase Typography bootstrap docs
219 .text-nowrap Prevents the text from wrapping Typography bootstrap docs
220 .small Create lighter, secondary text in any heading with a generic Typography None
221 .text-uppercase Makes text uppercase Typography bootstrap docs
222 .list-inline Change ul or ol list to be listed horizontally with a little margin between each li Typography None
223 .h1 - .h6 Apply heading styles to other elements. Make a paragraph look like an h1 Typography None
224 .list-inline Overrides a lists default style to be inline and block Typography None
225 .list-unstyled Removes all bullet styling from a ul or ol list Typography None
226 .mark For highlighting a run of text due to its relevance in another context, use the mark tag. Typography None
227 .position-* Not responsive, but a group of utility classes to add common position values. .position-(absolute, fixed, relative, static, sticky) Utilities bootstrap docs
228 .bg-* Background color utility classes: Utilities None
229 .sr-only Hide element to all devices except screen readers Utilities None
230 .text-break When you are building applications that have long strings or user generated content, this class breaks the long text so that it does not break the layout. Without this, the text would be as wide as the string itself. Utilities bootstrap docs
231 .stretched-link This class extends the clickable area of an anchor link to fill the parent container. The parent container must have a position:relative for this to work properly. Utilities None
232 .sr-only-focusable Combine .sr-only with .sr-only-focusable to show the element again when it’s focused by a user using a keyboard Utilities None
233 .p*-# Sometimes you need to add some margin or padding to element without writing a custom CSS selector. Set margin or padding, the side to apply the spacing, and lastly the size of the spacing (m,p)-(t|r|b|l|x|y|a)-(0,1,2,1.5,3) Utilities None
234 .text-*-* Aligns text left, right or center use choose breakpoint (xs|sm|md|lg|xl) then alignment (left, right, center) Utilities bootstrap docs
235 .align-content-* Added to the parent flexbox container to determing how the elements are aligned horizontally. Utility None
236 .align-items-* Class added to flexbox child items to specify if it should align towards the top or bottom of the container (start, end) Utility None
237 .align-self-* Used on flexbox items to align them vertically in relation to the parent container. If columns are used the items will align verticall. (start, end, center, baseline, or stretch (browser default) Utility None
238 .align-text-* A set of utility classes that are equivelant to writing the css property Utility bootstrap docs
239 .border-* A versatile border utility class that lets you add/remove borders on a side or change a border color. Utility None
240 .d-flex Sets the element to have have the style property Utility bootstrap docs
241 .d-none Sets the element to have have the style property Utility None
242 .d-*-* A responsive display utility class that lets you specify when a display property is applied to the element. Utility None
243 .visible Hides the visibility of an element but does not change their display property. Utility None
244 .flex-*-*-* Change the flexbox items layout, alignment, or size. Utility bootstrap docs
245 .float-*-* Responsive utility to float an element. Utility bootstrap docs
246 .h-* Height utility class that makes the element a percentage height of its parent element. Utility None
247 .justify-content-*-* Class specifies where the flex items will be positioned inside the container. Utility None
248 .m*-*-* Applies margin to an element using responsive breakpoints {property}{sides}-{breakpoint}-{size} Utility None
249 .p*-*-* Applies padding to an element using responsive breakpoints {property}{sides}-{breakpoint}-{size} Utility None
250 .w-* Width utility class that makes the element a percentage width of its parent element. Utility None
251 .clearfix Clears the floats of any child elements. Add this class to the parent element wrapping the floating elements. Utility bootstrap docs
252 .d-* Append the following to change the element display property (block, inline, inline-block) Utility None
253 .embed-responsive-* Class used to adjust responsive embed aspect ratio. Append one of the following for the desired aspect ratio (21by9, 16by9, 4by3, 1by1) Utility bootstrap docs
254 .embed-responsive-item By default responsive embeds apply to iframe, object, embed, and video tags. You can add .embed-responsive-item to any other element to have the same responsive styles applied Utility bootstrap docs
255 .font-* (italic, weight-bold, weight-light, weight-normal, monospace) Utility None
256 .m*-# Sometimes you need to add some margin or padding to element without writing a custom CSS selector. Set margin or padding, the side to apply the spacing, and lastly the size of the spacing (m,p)-(t|r|b|l|x|y|a)-(0,1,2,1.5,3) Utility None
257 .pos-f-t Positions an element fixed to the top of the viewport and full width. Utility None
258 .fixed-* This class makes an element fixed to the top/bottom of the browser window. Here is what the CSS ruleset looks like. Utility None
259 .align-* A set of utility classes that are equivelant to writing the css property Utility None

Filter table with Jupyter notebook Javascript.

In [26]:
js_code = """function filterTable(table, needle){
    var needle_ = needle.toLowerCase(),
        rows = Array.from(table.rows),
        results = rows
        .slice(1)
        .filter(row => !Array.from(row.cells)
                .some(cell => cell.textContent.toLowerCase().includes(needle_)))
    results.forEach((row)  => $(row).hide())
}

function resetTable(table){
    Array.from(table.rows).forEach(row => $(row).show())
}

var rows = document.getElementsByTagName('table'),
    [_, table, __] = rows; // second table
if(table){
    resetTable(table);
}
$('.btn-secondary').on('click', function(event){
    event.preventDefault();
    $('input').val("");
    resetTable(table);
    
})
$('#searchForm').on('submit', function(event){
    event.preventDefault();
    if(table){
       resetTable(table);
       filterTable(table, $('input').first().val())
    }
})"""
In [27]:
Javascript(js_code)
Out[27]:

Write the html table and JavaScript to a Nikola page that is a Chameleon template file.

In [174]:
from pathlib import Path
import os

from chameleon import PageTemplateFile
In [175]:
parent = Path(os.pardir)
pages_dir = parent.joinpath('pages')
In [176]:
page, = pages_dir.iterdir()
template = parent.joinpath('table_template.html')
print(template.read_text())
<!--
.. title: Searchable Table of Bootstrap Classes
.. slug: searchable-table-of-bootstrap-classes
.. date: 2019-11-12 15:54:14 UTC
.. tags: css, html
.. category: do-it-yourself
.. link:
.. description: A prgrammatically created searchable HTML table of all the Boostrap CSS classes.
.. type: text
-->

<div tal:omit-tag tal:repeat="item items" tal:content="structure item"></div>

In [177]:
page.write_text(PageTemplateFile(template).render(items=(query_form, table_html,)))
Out[177]:
70929

Answer Stack Overflow Question: How can I create a dictionary from two different row values in excel in python?

Answer Stack Overflow Question: How can I create a dictionary from two different row values in excel in python?

Create some sample data into an Excel spreadsheet.

In [29]:
from pathlib import Path
import tempfile
from subprocess import check_output, STDOUT
import shlex

from faker import Faker
import openpyxl
In [30]:
fake = Faker()
In [31]:
wb = openpyxl.Workbook()
ws = wb.active
In [32]:
def get_unique_keys(length, fake_attr, f):
    items = set()
    while len(items) < length:
        items.update(set((f(getattr(fake, fake_attr)()),)))
    return list(items)
In [33]:
LENGTH = 10
for row in (
    get_unique_keys(*args)
    for _ in range(3)
    for args in (
        (LENGTH, "uuid4", lambda x: x[:8]),
        (LENGTH, "catch_phrase", lambda x: x.split()[-1]),
    )
):
    ws.append(row)
In [34]:
path = Path(tempfile.mkdtemp()).joinpath('fake_xl.xlxs')
path
Out[34]:
PosixPath('/tmp/tmpgx8zlnfu/fake_xl.xlxs')
In [35]:
wb.save(path.as_posix())
In [36]:
check_output(shlex.split(f"mc cp {path} dokkuminio/mymedia/xlxs/"))
Out[36]:
b'`/tmp/tmpgx8zlnfu/fake_xl.xlxs` -> `dokkuminio/mymedia/xlxs/fake_xl.xlxs`\nTotal: 5.05 KB, Transferred: 5.05 KB, Speed: 1.07 MB/s\n'

A solution to the question.

In [53]:
import urllib.request
import xlrd
import itertools as it

url = "https://minio.apps.selfip.com/mymedia/xlxs/fake_xl.xlxs"

filepath, response = urllib.request.urlretrieve(url, "/tmp/test.xlxs") 

wb = xlrd.open_workbook(filepath)
ws = wb.sheet_by_index(0)

odd_rows, even_rows = it.tee(ws.get_rows()) # Get 2 iterables using itertools.tee
row_pairs = ( # generator expression to "chunk the rows into groups of 2"
    [[cell.value for cell in row] for row in rows] # 2D list of values
    for rows in zip(
        it.islice(odd_rows, 0, ws.nrows, 2), # Use itertools.islice
        it.islice(even_rows, 1, ws.nrows, 2),
    )
)
print([dict(zip(*row_pair)) for row_pair in row_pairs])

# Another way to chunk the rows into pairs is like this:

print([
    dict(zip(*[[cell.value for cell in pair] for pair in pairs]))
    for pairs in it.zip_longest(*it.repeat(iter(ws.get_rows()), 2))
])
[{'2a5de626': 'algorithm', '86ce99a2': 'implementation', 'e6b481ba': 'adapter', 'bc85c996': 'capability', '4edfb828': 'array', '05d79ce2': 'definition', 'b9b5ae33': 'knowledgebase', 'f0da7366': 'complexity', '39a48259': 'methodology', '1ee95d9e': 'strategy'}, {'01bc389d': 'neural-net', 'd5d16b0c': 'monitoring', 'd9fb3a8d': 'installation', '8c7a049f': 'moratorium', 'f3d9aa0e': 'help-desk', 'd0e8d371': 'paradigm', '9e33f679': 'complexity', '6354affc': 'core', '606c4eb6': 'groupware', '97741196': 'strategy'}, {'76ae32df': 'algorithm', '942654da': 'task-force', '462fa31b': 'ability', '584df007': 'adapter', 'f6293960': 'attitude', 'afd8fa00': 'knowledgebase', '4c5f2c49': 'alliance', '6d76c690': 'collaboration', '3018a22b': 'solution', '034f1bb2': 'access'}]
[{'2a5de626': 'algorithm', '86ce99a2': 'implementation', 'e6b481ba': 'adapter', 'bc85c996': 'capability', '4edfb828': 'array', '05d79ce2': 'definition', 'b9b5ae33': 'knowledgebase', 'f0da7366': 'complexity', '39a48259': 'methodology', '1ee95d9e': 'strategy'}, {'01bc389d': 'neural-net', 'd5d16b0c': 'monitoring', 'd9fb3a8d': 'installation', '8c7a049f': 'moratorium', 'f3d9aa0e': 'help-desk', 'd0e8d371': 'paradigm', '9e33f679': 'complexity', '6354affc': 'core', '606c4eb6': 'groupware', '97741196': 'strategy'}, {'76ae32df': 'algorithm', '942654da': 'task-force', '462fa31b': 'ability', '584df007': 'adapter', 'f6293960': 'attitude', 'afd8fa00': 'knowledgebase', '4c5f2c49': 'alliance', '6d76c690': 'collaboration', '3018a22b': 'solution', '034f1bb2': 'access'}]

Create Bootstrap Components with Python str and dict Types

Create Boostrap components with Python str and dict types.

In [1]:
from chamelboots.bootstrap.components.constants import (
    ContextualClassNames,
    ATTRIBUTE_LOOKUP,
    ALERT_CLASSES_ATTRIBS,
    COMPONENT_CLASS_NAMES,
)
from chamelboots.bootstrap.components.alerts import ALERT_COMPONENTS
from chamelboots import ChameleonTemplate, TalStatement

Possible contextual class names as taken from Boostrap documentation.

In [2]:
ContextualClassNames.__members__
Out[2]:
mappingproxy({'PRIMARY': <ContextualClassNames.PRIMARY: 'primary'>,
              'SECONDARY': <ContextualClassNames.SECONDARY: 'secondary'>,
              'SUCCESS': <ContextualClassNames.SUCCESS: 'success'>,
              'DANGER': <ContextualClassNames.DANGER: 'danger'>,
              'WARNING': <ContextualClassNames.WARNING: 'warning'>,
              'INFO': <ContextualClassNames.INFO: 'info'>,
              'LIGHT': <ContextualClassNames.LIGHT: 'light'>,
              'DARK': <ContextualClassNames.DARK: 'dark'>})

TODO

  • Define __str__ method on ChameleonTemplate so the html_string displays on print.
  • Define __str__ method on ALERT_COMPONENTS so the html_string displays on print.
  • Define __repr__ method on ALERT_COMPONENTS and ChameleonTemplateso the html_string displays on repr.
In [3]:
# Does Chameleon add to or overwrite attributes?
div = ChameleonTemplate(tal_statements=(TalStatement("attributes", "extra"),))
print(div)
div
<div tal:attributes="extra"></div>
Out[3]:
<ChameleonTemplate: '<div tal:attributes="extra"></div>'>
In [4]:
ALERT_COMPONENTS["AlertInfo"](inner_content="foo")
Out[4]:
< AlertInfo :  <div class="alert alert-info" role="alert">foo</div> >
In [5]:
# Does Chameleon add to or overwrite attributes?
# Yes.
ALERT_COMPONENTS["AlertInfo"](attrib={}, inner_content="")
Out[5]:
< AlertInfo :  <div></div> >
In [6]:
ALERT_COMPONENTS["AlertInfo"](inner_content="foo content")
Out[6]:
< AlertInfo :  <div class="alert alert-info" role="alert">foo content</div> >
In [7]:
ATTRIBUTE_LOOKUP
Out[7]:
{'AlertPrimary': {'class': 'alert alert-primary', 'role': 'alert'},
 'AlertSecondary': {'class': 'alert alert-secondary', 'role': 'alert'},
 'AlertSuccess': {'class': 'alert alert-success', 'role': 'alert'},
 'AlertDanger': {'class': 'alert alert-danger', 'role': 'alert'},
 'AlertWarning': {'class': 'alert alert-warning', 'role': 'alert'},
 'AlertInfo': {'class': 'alert alert-info', 'role': 'alert'},
 'AlertLight': {'class': 'alert alert-light', 'role': 'alert'},
 'AlertDark': {'class': 'alert alert-dark', 'role': 'alert'}}
In [8]:
COMPONENT_CLASS_NAMES
Out[8]:
['AlertPrimary',
 'AlertSecondary',
 'AlertSuccess',
 'AlertDanger',
 'AlertWarning',
 'AlertInfo',
 'AlertLight',
 'AlertDark']

Experiment with recreating this HTML

<div class="alert alert-success" role="alert">
  <h4 class="alert-heading">Well done!</h4>
  <p>Aww yeah, you successfully read this important alert message. This example text is going to run a bit longer so that you can see how spacing within an alert works with this kind of content.</p>
  <hr>
  <p class="mb-0">Whenever you need to, be sure to use margin utilities to keep things nice and tidy.</p>
</div>

Inject any arbitrary Boostrap attribs into a ChameleonTemplate

In [9]:
from chamelboots import ChameleonTemplate, TalStatement
from IPython.display import display, HTML
In [10]:
chameleon_template = ChameleonTemplate(
    "h4",
    tal_statements=(TalStatement("attributes", "attrib"),),
    inner_content="Well done!",
)
chameleon_template
Out[10]:
<ChameleonTemplate: '<h4 tal:attributes="attrib">Well done!</h4>'>

All classes in Boostrap

The following example seems convenient enough for creating Bootstrap styled HTML.

Issue: Remembering all the class names and when to use them.

In [11]:
chameleon_template.render(attrib={"class": "alert-heading"})
Out[11]:
'<h4 class="alert-heading">Well done!</h4>'

These classes were programmatically generated.

But are rather confusing to use.

In [12]:
ALERT_COMPONENTS["AlertSuccess"](inner_content="")
Out[12]:
< AlertSuccess :  <div class="alert alert-success" role="alert"></div> >
In [13]:
from chamelboots.constants import HTML_PARSER, FAKE
from chamelboots import ChameleonTemplate, TalStatement
from lxml import etree
<div class="accordion" id="accordionExample">
  <div class="card">
    <div class="card-header" id="headingOne">
      <h2 class="mb-0">
        <button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
          Collapsible Group Item #1
        </button>
      </h2>
    </div>

    <div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordionExample">
      <div class="card-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
      </div>
    </div>
  </div>
  <div class="card">
    <div class="card-header" id="headingTwo">
      <h2 class="mb-0">
        <button class="btn btn-link collapsed" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
          Collapsible Group Item #2
        </button>
      </h2>
    </div>
    <div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionExample">
      <div class="card-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
      </div>
    </div>
  </div>
  <div class="card">
    <div class="card-header" id="headingThree">
      <h2 class="mb-0">
        <button class="btn btn-link collapsed" type="button" data-toggle="collapse" data-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
          Collapsible Group Item #3
        </button>
      </h2>
    </div>
    <div id="collapseThree" class="collapse" aria-labelledby="headingThree" data-parent="#accordionExample">
      <div class="card-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
      </div>
    </div>
  </div>
</div>
In [14]:
ARBITRARY_CLASS_NAME = "accordian"
DATA_PARENT_ID = "accordionExample"
accordian = ChameleonTemplate(
    tal_statements=(TalStatement("attributes", "attrib"),)
).render(attrib={"class": ARBITRARY_CLASS_NAME, "id": DATA_PARENT_ID})
accordian  # a string
Out[14]:
'<div class="accordian" id="accordionExample"></div>'
In [15]:
from bs4 import BeautifulSoup

bootstrap_card = """
<div class="card">
    <div class="card-header" id="headingOne">
      <h2 class="mb-0">
        <button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
          Collapsible Group Item #1
        </button>
      </h2>
    </div>

    <div id="collapseOne" class="collapse" aria-labelledby="headingOne" data-parent="#accordionExample">
      <div class="card-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
      </div>
    </div>
</div>
"""

print(BeautifulSoup(bootstrap_card, 'html.parser').prettify())
<div class="card">
 <div class="card-header" id="headingOne">
  <h2 class="mb-0">
   <button aria-controls="collapseOne" aria-expanded="true" class="btn btn-link" data-target="#collapseOne" data-toggle="collapse" type="button">
    Collapsible Group Item #1
   </button>
  </h2>
 </div>
 <div aria-labelledby="headingOne" class="collapse" data-parent="#accordionExample" id="collapseOne">
  <div class="card-body">
   Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
  </div>
 </div>
</div>

In [16]:
HTML(bootstrap_card)
Out[16]:

Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
<div class="card">
    <div class="card-header" id="headingOne">
      <h2 class="mb-0">
        <button class="btn btn-link" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
          Collapsible Group Item #1
        </button>
      </h2>
    </div>

    <div id="collapseOne" class="collapse" aria-labelledby="headingOne" data-parent="#accordionExample">
      <div class="card-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
      </div>
    </div>
</div>
<div class="card">
    <div class="card-header" id="headingTwo">
      <h2 class="mb-0">
        <button class="btn btn-link collapsed" type="button" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
          Collapsible Group Item #2
        </button>
      </h2>
    </div>
    <div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionExample">
      <div class="card-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
      </div>
    </div>
</div>