Skip to main content

One post tagged with "Python"

View All Tags

· 7 min read

In this tutorial I will be walking through the development of my HTML builder Python package.

The completed package is available at bvennes/html_builder on GitHub.

Creating a Clientside Project With Python

The purpose of the HTML builder is to programmatically create an HTML file using Python. In the future, however, I would like to make SCSS and JavaScript builders as well so that the entirety of a web application could be built using Python.

HTML is a good place to start because the structure of an HTML file is fairly simple. HTML tags look like <tag-name class="class-1 class-2"></tag-name> and are nested to build out component hierarchies.

An HTML tag can be broken down into 4 major components:

  • name of the tag
  • list of classes for the tag
  • additional attributes like onclick or style
  • child tags or text

Html Python Class

To create an HTML tag in Python, I created a basic object called Html holding access to the HTML tag's name, class names, and attributes like onclick: doSomething().

It would also be possible for the classes list to be given as an attribute, since class is technically just an attribute, but this is a good opportunity to make use of Python's *args functionality.

Here is what the initial code looked like for the Html object:

class Html:
def __init__(self, tag_name, *class_names, **attributes):
"""Initializes a new html tag."""
self.name = tag_name
self.class_names = list(class_names)
self.attributes = attributes
self.children = []

Render Method

In order to output the Html object as a string, I added a render method.

def render(self):
"""Renders the html tag as a string."""
html = f'<{self.name}'

if self.class_names.__len__() > 0:
classes_list = ' '.join(self.class_names)
html += f' class="{classes_list}"'

for key, value in self.attributes.items():
html += f' {key}="{value}"'

html += f'></{self.name}>'

return html

To render the HTML tag as a string, we maintain an html element that begins with <self.name, add the classes as a list separated by a space, add the sets of attribute key/value pairs, and finally close it off with ></self.name>.

With both the __init__ and render methods completed, the Html class looks like:

class Html:
def __init__(self, tag_name, *class_names, **attributes):
"""Initializes a new html tag."""
self.name = tag_name
self.class_names = list(class_names)
self.attributes = attributes
self.children = []

def render(self):
"""Renders the html tag as a string."""
html = f'<{self.name}'

if self.class_names.__len__() > 0:
classes_list = ' '.join(self.class_names)
html += f' class="{classes_list}"'

for key, value in self.attributes.items():
html += f' {key}="{value}"'

html += f'></{self.name}>'

return html

Html Class Testing

Before testing this class out, let's setup the full html_builder Python package.

First, create a new directory called html_builder.

Add a blank file within this directory called __init__.py

Add a subdirectory to html_builder also called html_builder.

Within the html_builder subdirectory, add a file named html.py and copy the Html class from above.

Then, create a test script in the same folder as the top-level html_builder directory named html_test.py.

# html_test.py
from html_builder.html import Html

button = Html('button', 'class-1', 'class-2', onclick="alert('Hello world!')")

print(button.render())

The output of this Python script should be <button class="class-1 class-2" onclick="alert('Hello world!')"></button>. Let's copy this to a file named button.html and open it in a web browser. We should see a tiny button with no text. If we click on it, the window alerts us with the message Hello world!. That's a promising start!

But users will want to be able to add text to their button. In order to do this, we want our button to be able to contain some child elements, like this:

<button class="class-1 class-2" onclick="alert('Hello world!')">
Click me!
</button>

Html Children

Let's add a child element to our button.

button = Html('button', onclick="alert('Hello world!')")
button.children += ['Click me!']

However, our render method isn't rendering any of our children, so let's fix that by adding a method called render_children(). We will make it a private method so that we can encapsulate any additional logic that might occur while rendering the tag's children. To make the method private, add __ before the method name.

This is also a good time to make the HTML format nicely when printed using newline characters and spaces.

def __render_children(self):
"""Renders the tag's children"""
rendered_children = ''

for child in self.children:
rendered_children += '\n '
if type(child) is Html:
rendered_children += child.render().replace('\n','\n ')
else:
rendered_children += child

rendered_children += '\n'

return rendered_children

Let's also modify the render to use the new private method.

def render(self):
"""Renders the html tag as a string."""
html = f'<{self.name}'

if self.class_names.__len__() > 0:
classes_list = ' '.join(self.class_names)
html += f' class="{classes_list}"'

for key, value in self.attributes.items():
html += f' {key}="{value}"'

rendered_children = self.__render_children()

html += f'>{rendered_children}</{self.name}>'

return html

The Html class should now look like this:

# html.py
class Html:
def __init__(self, tag_name, *class_names, **attributes):
"""Initializes a new html tag."""
self.name = tag_name
self.class_names = list(class_names)
self.attributes = attributes
self.children = []

def render(self):
"""Renders the html tag as a string."""
html = f'<{self.name}'

if self.class_names.__len__() > 0:
classes_list = ' '.join(self.class_names)
html += f' class="{classes_list}"'

for key, value in self.attributes.items():
html += f' {key}="{value}"'

rendered_children = self.__render_children()

html += f'>{rendered_children}</{self.name}>'

return html

def __render_children(self):
"""Renders the tag's children"""
rendered_children = ''

for child in self.children:
rendered_children += '\n '
if type(child) is Html:
rendered_children += child.render().replace('\n','\n ')
else:
rendered_children += child

rendered_children += '\n'

return rendered_children

Looks good! Now we'll want to update our test script to make use of the new functionality. I've added div and title elements to test out nested HTML.

Testing Child HTML Tags

# html_test.py
from html_builder.html_builder.html import Html

div = Html('div')

title = Html('h1')

button = Html('button', onclick="alert('Hello world!')")

button.children += ['Click me!']
title.children += ['HTML Builder Test']
div.children += [title, button]

print(div.render())

After running the html_test.py script we should see the output

<div>
<h1>
HTML Builder Test
</h1>
<button onclick="alert('Hello world!')">
Click me!
</button>
</div>

After copying the output to test.html and reloading the browser, it should show our title and a button that says 'Click me!'.

Rendering to an HTML File

At this point, our builder is just about done. But it might be helpful for our users to render HTML directly into a file. So I've added an output_file parameter to the render method so that users can specify a path where the rendered HTML should go. Before returning the HTML as a string, I've also added a section for opening the file if it is given, writing to it, and closing it.

def render(self, output_file_path=None):
"""Renders the html tag as a string."""
html = f'<{self.name}'

if self.class_names.__len__() > 0:
classes_list = ' '.join(self.class_names)
html += f' class="{classes_list}"'

for key, value in self.attributes.items():
html += f' {key}="{value}"'

rendered_children = self.__render_children()

html += f'>{rendered_children}</{self.name}>'

if output_file_path != None:
output_file = open(output_file_path, 'w')
output_file.write(html)
output_file.close()

return html

To verify this behavior works correctly, I've specified the output file in the test script.

from html_builder.html_builder.html import Html

div = Html('div')

title = Html('h1')

button = Html('button', onclick="alert('Hello world!')")

button.children += ['Click me!']
title.children += ['HTML Builder Test']
div.children += [title, button]

print(div.render('./test.html'))

Now when we run the script, the HTML is printed to both the output terminal and the test.html file. Neat!