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?
SnakeMD 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:
C:\Users\jerem>pip install SnakeMD Collecting SnakeMD Using cached SnakeMD-0.10.0-py3-none-any.whl (17 kB) Installing collected packages: SnakeMD Successfully installed SnakeMD-0.10.0
As of writing, the current version of SnakeMD is 0.10.0. 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.
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 component 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 component 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 components such as Paragraphs, MDLists, Headers, 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("Example")
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("Example") <snakemd.generator.Document object at 0x0000015253701360>
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_header("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_header("This is the Title of the Page") <snakemd.generator.Header object at 0x0000015253759930> doc.add_paragraph("This is an example of a paragraph") <snakemd.generator.Paragraph object at 0x0000015253759F00> doc.add_ordered_list(["This", "is", "an", "ordered", "list"]) <snakemd.generator.MDList object at 0x0000015253700FD0> doc.add_code("print('Hello World')", "python") <snakemd.generator.Paragraph object at 0x0000015253BFEDD0> doc.add_horizontal_rule() <snakemd.generator.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---"
Note: this website uses backticks to format code (like Markdown), so they’ve been replaced with primes for viewing only. The actual library will print proper backticks.
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 the Components Directly
Each component 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.InlineText("Google", url="google.com"), snakemd.InlineText("Amazon", url="amazon.com"), ] doc.add_element(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 `InlineText` 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 think with a paragraph:
p = snakemd.Paragraph([ "The text above shows links, but ", snakemd.InlineText("here", url="therenegadecoder.com"), " is another link." ]) doc.add_element(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](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/snake/#snakemd.generator.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](therenegadecoder.com) is another link. The best way to insert links is to use [this](https://www.snakemd.io/en/latest/snake/#snakemd.generator.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 website. 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_header("Articles", level=2) articles =  for program in repo[language]: link = snakemd.InlineText( str(program), url=program.documentation_url() ) articles.append(link) doc.add_element(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!
A fairly popular repo, GarminDB, 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("activities") doc.add_header("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.
Another cool repo I saw in the wild was Zotero2MD. 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_header(level=1, text="Highlights") annots =  for h in annotations: formatted_annotation = self.format_annotation(h) if isinstance(formatted_annotation, tuple): annots.append(formatted_annotation) annots.append(formatted_annotation) else: annots.append(formatted_annotation) self.doc.add_element(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:
- 3 Things Software Developers Don’t Need to Know
- The Complete Guide to Subete: A Python Library for Browsing Code Snippets
Otherwise, take care! See you next time.
Recent Code Posts
I've seen a lot of folks share code on Discord, but some ways are better than others. Let's compare a few of the different ways.
Java has featured lambda expressions since Java 8, and they are more than a slightly bit cursed.