The Complete Guide to SnakeMD: A Python Library for Generating Markdown

The Complete Guide to SnakeMD: A Python Library for Generating Markdown Featured Image

Markdown is one of my favorite file formats as someone who doesn’t know a lot of frontend. If you’re like me, then you’ll be pleased to know that a Python library exists for dynamically generating it: SnakeMD. Not only that, but today I’m going to share with you the complete guide to using it.

Table of Contents

What Is SnakeMD?

SnakeMDOpens in a new tab. is a library that I wrote to generate Markdown code. For those unfamiliar with Markdown, it’s basically shorthand HTML. Rather than writing all those messy tags, you can use a much cleaner syntax:

  • Lists? Use `- word`!
  • Paragraphs? Just write text!
  • Italics? Use `*word*`
  • Bold? Use `**word**`
  • Headings? Use `#`, `##`, and `###`

Of course, there’s more, but this should give you a good idea of what Markdown actually is. Of course, if you use SnakeMD, you don’t need to know Markdown at all! You can make use of the various functions to build up the document programmatically.

How Do I Install SnakeMD?

To install SnakeMD, you can make use of `pip`:

pip install snakemd

Currently, SnakeMD has no dependencies, so you should see something like this:

pip install --pre snakemd
Collecting snakemd
  Using cached SnakeMD-2.0.0b1-py3-none-any.whl (17 kB)
Installing collected packages: snakemd
Successfully installed snakemd-2.0.0b1

As of writing, the current version of SnakeMD is 2.0.0b1. All subsequent documentation will be written with that version in mind.

How Do I Use SnakeMD?

In order to learn how to use SnakeMD, we need to take a moment to talk about how SnakeMD is designed.

SnakeMD Structure

Broadly, SnakeMD is a library that is designed with two kinds of folks in mind: those who just need a quick way to make some Markdown and those who want a little more control over how that Markdown looks.

As a result, there are actually two different ways to make use of SnakeMD. The first is to create a document and use the set of Document methods. The second is to create a Document and create each individual block-level elements as needed.

My intent with this design was to mirror a library I like to use, Plotly. When I don’t need a lot of configuration, there are a set of functions I can use. When I do want a bit more control, I can use the underlying objects.

Ultimately, each element of a Markdown document is modeled as an object. Therefore, you can use the Document object as-is, or you can make use of the various other block-level elements such as Paragraphs, MDLists, Headings, Tables, etc.

Creating a Markdown Document

Regardless of how you choose to make use of SnakeMD, you will always need to create a document. To do that, I recommend making use of the top-level function: `new_doc()`.

import snakemd

doc = snakemd.new_doc()

This will create a document from which we can add elements. If you run a line like this yourself, here’s what you might see:

import snakemd

>>> snakemd.new_doc()
<snakemd.document.Document object at 0x0000024B32ADF3A0>

Up next, we’ll take a look at how to use some of the convenience methods for updating this document.

Using the Convenience Methods

As mentioned, every document can be updated by a set of convenience methods. For example, here are a few that might be useful to you:

# Adds a H1 element to the document
doc.add_heading("This is the Title of the Page")

# Adds a paragraph to the document
doc.add_paragraph("This is an example of a paragraph")

# Adds an ordered list to the document
doc.add_ordered_list(["This", "is", "an", "ordered", "list"])

# Adds a code block to the document
doc.add_code("print('Hello World')", "python")

# Adds a horizontal rule to the document
doc.add_horizontal_rule()

And of course, this is what these code segments look like when you actually run them:

>>> doc.add_heading("This is the Title of the Page")
<snakemd.elements.Heading object at 0x0000024B32707D60>

>>> doc.add_paragraph("This is an example of a paragraph")
<snakemd.elements.Paragraph object at 0x0000015253759F00>

>>> doc.add_ordered_list(["This", "is", "an", "ordered", "list"])
<snakemd.elements.MDList object at 0x0000015253700FD0>

>>> doc.add_code("print('Hello World')", "python")
<snakemd.elements.Code object at 0x0000015253BFEDD0>

>>> doc.add_horizontal_rule()
<snakemd.elements.HorizontalRule object at 0x0000015253BFECE0>

As you can see, each function returns the actual element generated. In general though, you don’t need to use them. That’s because the document contains all these elements already. Take a look:

str(doc)
"# This is the Title of the Page\n\nThis is an example of a paragraph\n\n1. This\n2. is\n3. an\n4. ordered\n5. list\n\n```python\nprint('Hello World')\n```\n\n***"

Or in an actually readable format:

print(doc)
# This is the Title of the Page

This is an example of a paragraph

1. This
2. is
3. an
4. ordered
5. list

```python
print('Hello World')
```

***

Now, that’s pretty cool. Up next, we’ll look at some more advanced customization options.

Using Blocks Directly

Each block-level element (a.k.a. block) can be constructed by hand. Sometimes this needs to be done to mix and match elements. For example, I often need to put links into items in a list, so I might do the following:

import snakemd

items = [
  snakemd.Inline("Google", link="google.com"),
  snakemd.Inline("Amazon", link="amazon.com"),
]

doc.add_block(snakemd.MDList(items))

And so here’s the same doc but with a list of links at the bottom:

print(doc)
# This is the Title of the Page

This is an example of a paragraph

1. This
2. is
3. an
4. ordered
5. list

′′′python
print('Hello World')
′′′

***

- [Google](google.com)
- [Amazon](amazon.com)

For context, the Inline object is used when we want to customize only a piece of a broader element such as a list or paragraph. For example, we could do a similar thing with a paragraph:

p = snakemd.Paragraph([
  "The text above shows links, but ",
  snakemd.Inline("here", link="https://therenegadecoder.com"),
  " is another link."
])

doc.add_block(p)

Which, of course, updates the document as follows:

print(doc)
# This is the Title of the Page

This is an example of a paragraph

1. This
2. is
3. an
4. ordered
5. list

′′′python
print('Hello World')
′′′

---

- [Google](google.com)
- [Amazon](amazon.com)

The text above shows links, but [here](https://therenegadecoder.com) is another link.

Personally, I find this a bit cumbersome, so I tend to use the insert_link() method instead:

doc.add_paragraph("The best way to insert links is to use this function") \
  .insert_link(
    "this", 
    "https://www.snakemd.io/en/latest/docs/element-api/#snakemd.Paragraph.insert_link"
  )

Which then gives us this document:

print(doc)
# This is the Title of the Page

This is an example of a paragraph

1. This
2. is
3. an
4. ordered
5. list

′′′python
print('Hello World')
′′′

---

- [Google](google.com)
- [Amazon](amazon.com)

The text above shows links, but [here](https://therenegadecoder.com) is another link.

The best way to insert links is to use [this](https://www.snakemd.io/en/latest/docs/element-api/#snakemd.Paragraph.insert_link) function

Outside of that, there are lots of ways to use SnakeMD. I’m sure folks will find other ways to use it!

Real World Use of SnakeMD

If you head over to the SnakeMD repo, you’ll see that a lot of folks are actually using SnakeMD. At the time of writing, it’s in use in 12 repos, a few of which are mine. As a result, I figured I’d share some ways it’s being used.

Sample Programs Website Automation

One of my most recent uses of SnakeMD is in automating the Sample Programs websiteOpens in a new tab.. After all, all of the web pages are written in Markdown, so it was a natural fit. For example, here’s a method I wrote to generate the list of articles for a given programming language:

def _add_language_article_section(doc: snakemd.Document, repo: subete.Repo, language: str):
    """
    Generates a list of articles for each language page.
    :param snakemd.Document doc: the document to add the section to.
    :param subete.Repo repo: the repo to pull from.
    :param str language: the language to add to the document in its lookup form (e.g., Python).
    """
    doc.add_heading("Articles", level=2)
    articles = []
    for program in repo[language]:
        link = snakemd.Inline(
            str(program),
            link=program._sample_program_doc_url
        )
        articles.append(link)
    doc.add_block(snakemd.MDList(articles))

As you can see, I manually generate a list using the program name and its documentation URL. Now, that’s pretty cool!

Garmin Database

A fairly popular repo, GarminDBOpens in a new tab., has apparently been using SnakeMD to render Markdown in Jupyter notebooks. I didn’t do too much digging, but I thought this was cool!

doc = snakemd.new_doc()

doc.add_heading("Activities Report")
doc.add_paragraph("Analysis of all activities in the database.")

doc.add_table(
    ['Type', 'Count'],
    [
        ["Total activities", Activities.row_count(garmin_act_db)],
        ["Total Lap records", ActivityLaps.row_count(garmin_act_db)],
        ["Activity records", ActivityRecords.row_count(garmin_act_db)],
        ["Fitness activities", Activities.row_count(garmin_act_db, Activities.type, 'fitness')],
        ["Recreation activities", Activities.row_count(garmin_act_db, Activities.type, 'recreation')]
    ])

Tables are one of those features I neglected to share, so here’s what that might look like in action.

Zotero2MD

Another cool repo I saw in the wild was Zotero2MDOpens in a new tab.. It’s a fairly small project that pulls annotations from Zotero, a citation manager, and dumps them into Markdown files. It really take too long to find a function making use of the MDList object:

def create_annotations_section(self, annotations: List) -> None:
    """Generate the annotation sections (titled "Highlights")
    In Zotero, an annotation is a highlighted text with the possibility of having related comment and tag(s).
    In addition, a note can also be added to a page without any highlight. This is also considered an annotation.
    The itemType="annotation" in the API response of both scenarios above.
    Parameters
    ----------
    annotations: List[Dict]
        A list containing all annotations of a Zotero Item.
    Returns
    -------
    None
    """
    self.doc.add_heading(level=1, text="Highlights")
    annots = []
    for h in annotations:
        formatted_annotation = self.format_annotation(h)
        if isinstance(formatted_annotation, tuple):
            annots.append(formatted_annotation[0])
            annots.append(formatted_annotation[1])
        else:
            annots.append(formatted_annotation)
    self.doc.add_block(MDList(annots))

To be honest, it’s really cool to see stuff like this in the wild making use of one of my libraries. Hopefully, more folks will make use of it soon!

What Other Libraries Would You Like to See?

With all that said, I think I’ve talked enough about SnakeMD. The last thing I’ll mention is next steps. Specifically, if you’re looking to go from dynamic markdown to a document like a PDF, I have a guide for that. It’s served a lot of my students well ever since I created markdown templates for them.

As always, if you liked this article and would like to see more like it, consider showing your support. One quick way to show your support is to keep browsing:

Otherwise, take care! See you next time.

The Python Docs You Didn't Know You Needed (2 Articles)—Series Navigation

After toying with Python for a long time, I’m come to realize that both the standard libraries and 3rd party libraries are a major draw of the language. Very rarely do you ever need to write anything from scratch. That said, despite this amazing ecosystem, it can be hard to make sense of all the libraries and the often limited documentation. As a result, I decided to make a series where I share a library and how to is it.

Jeremy Grifski

Jeremy grew up in a small town where he enjoyed playing soccer and video games, practicing taekwondo, and trading Pokémon cards. Once out of the nest, he pursued a Bachelors in Computer Engineering with a minor in Game Design. After college, he spent about two years writing software for a major engineering company. Then, he earned a master's in Computer Science and Engineering. Today, he pursues a PhD in Engineering Education in order to ultimately land a teaching gig. In his spare time, Jeremy enjoys spending time with his wife, playing Overwatch and Phantasy Star Online 2, practicing trombone, watching Penguins hockey, and traveling the world.

Recent Code Posts