How to Migrate to SnakeMD 2.0.0

How to Migrate to SnakeMD 2.0.0 Featured Image

Later this April 2023, SnakeMD will be officially moving v2.0.0 out of beta. To help users through that transition, I put together this migration guide.

Table of Contents

Rationale

For the better part of the last two years, SnakeMD was a proof-of-concept project for myself. It originally came out of efforts to automate markdown generation for a variety of personal projects, such as Sample Programs READMEs and Jekyll documentation. Having duplicated so much Markdown code, I finally decided to consolidate it all into one Python module, which became known as SnakeMD.

Over the last couple years, SnakeMD has matured into a stable tool for generating Markdown in Python. Further, folks in the open-source community have actually come to use the tool for their own needs. This has led to a variety of requested features and changes resulting in a more complete library.

However, in all this development, I felt the library was becoming somewhat bloated and messy. As a result, I decided to perform some refactoring, redesigning, and thorough testing to polish up the existing tool. In those efforts, several changes were made that break backwards compatibility. The tradeoff is a mature library that is ready for more general use. To ease folks into this change, I’ve put together this article as a transition guide.

Overview

As indicated in the version history, SnakeMD is jumping from v0.x to v2.x. The reason for this sudden jump in version is due to some early errors in my use of PyPI. Very early development of SnakeMD was put under v1.x before I decided to convert things over to a proper v0.x alpha. Turns out that deleted versions cannot be reused in PyPIOpens in a new tab., so I opted to jump straight to v2.x.

As far as big picture changes are concerned, most changes can be grouped into a few categories: removed, replaced, added, and improved. The following overview is copied directly from the version history page of SnakeMD:

  • v2.0.0b2
    • Converted all code snippets in docs to doctests
    • Reworked string input for Quote to pass directly through raw
    • Updated language around parameters in documentation to provide a list of possible inputs and their effects
    • Replaced url parameter with link parameter in insert_link() method of Paragraph
  • v2.0.0b1
    • Removed several deprecated items:
      • Classes
        • MDCheckList
        • CheckBox
        • Verification
      • Methods
        • Document.add_element()
        • Document.add_header()
        • Document.check_for_errors()
        • Inline.verify_url()
        • Paragraph.verify_urls()
        • Paragaph.is_text()
      • Parameters
        • name from new_doc and Document
        • code and lang from Paragraph
        • quote from Paragaph
        • render() and verify() from the entire repository
    • Replaced several deprecated items:
      • Classes
        • Inline replaces InlineText
        • Heading replaces Header
      • Methods
        • Inline.is_link() replaces Inline.is_url()
        • Document.dump() replaces Document.output_page()
      • Parameters
        • link replaces url in Inline
    • Added several new features:
      • Included a Quote block which allows for quote nesting
      • Incorporated ValueError exceptions in various class constructors
      • Started a resources page in documentation
      • Created a requirements file at the root of the repo to aid in development
    • Improved various aspects of the repo:
      • Expanded testing to 163 tests for 100% coverage
      • Clarified design of Inline to highlight precedence
      • Cleaned up documentation of pre-release version directives
      • Expanded types of inputs on various classes for quality of life
      • Changed behavior of horizontal rule to avoid clashes with list items
      • Fixed bugs in logs and expanded logging capabilities
      • Standardized docstring formatting
      • Updated README automation to use latest features

The remainder of this document goes over the major changes that folks may need to make to get their code up and running again. Later, I’ll share a few examples of actual transitions conducted on existing libraries that use SnakeMD.

Breaking Changes

In the transition from v0.x to v2.x, there are a variety of breaking changes worth covering.

MDChecklist and CheckBox Are Now Under MDList

If you were a user of the MDChecklist or Checkbox subclasses, those have both been removed. In their place, the MDList constructor now supports everything you need:

>>> from snakemd import MDList

>>> checklist = MDList(
...   ["first", "second", "third"],
...   checked=False
... )

>>> print(checklist)
- [ ] first
- [ ] second
- [ ] third

>>> checklist = MDList(
...   ["first", "second", "third"],
...   checked=True
... )

>>> print(checklist)
- [X] first
- [X] second
- [X] third

>>> checklist = MDList(
...   ["first", "second", "third"],
...   checked=[True, True, False]
... )

>>> print(checklist)
- [X] first
- [X] second
- [ ] third

In other words, there is no longer a need to explicitly construct an MDChecklist or CheckBox objects. The checked parameter exists to generate a checklist. Note: the list input must match the length of the top-level list (i.e., nested lists are not considered for the checking).

Elements Are Now Blocks

This is more of an internal change, but the idea is that everything added to a document is an element. Blocks are explicitly standalone elements that are separated by empty lines. As a result, the add_element() method no longer exists and has been replaced by add_block():

>>> from snakemd import Document, Heading

>>> doc = Document()

>>> doc.add_block(Heading("New Feature", 2))
<snakemd.elements.Heading object at 0x0000019F53147610>

>>> print(doc)
## New Feature

Documents No Longer Require Names

As hinted above, document objects no longer require a filename as input. The idea with this change is that a document can have as many names as you might like. To facilitate this change, the naming mechanism has been shifted to the output:

>>> import snakemd

>>> doc = snakemd.new_doc()

>>> doc.dump("README")

Notice that the output function is called dump(). The method output_page() also no longer exists as a consequence of this change.

Paragraphs No Longer Support Code or Quotes

For some time, Paragraphs were a generic block that could be converted into code blocks and blockquotes. This worked fine for most cases but left a lot to be desired for slightly more complicated structures like nesting markdown in quotes or nesting code. The result is three separate classes that serve clear purposes: Paragraphs, Quotes, and Code. Here’s each in action:

>>> from snakemd import Paragraph, Quote, Code

>>> paragraph = Paragraph("This is a paragraph")

>>> print(paragraph)
This is a paragraph

>>> quote = Quote(["This", "is", "a", "quote"])

>>> print(quote)
> This
> is
> a
> quote

>>> code = Code("m = 1.2")

>>> print(code)
```generic
m = 1.2
```

Another consequence of this change is that Paragraphs are always text-only objects, so the is_text() method no longer needs to exist. Likewise, the code and quote parameters no longer exist.

Both InlineText and Header Have Been Renamed

In the process of cleaning up the API, better names have been chosen for a handful of classes. Namely, InlineText—which allows for images and links—is broader than a text-only class. It’s now known as Inline. Likewise, headings have been incorrectly called Headers for some time in the repo. Therefore, Headers are known as Headings:

>>> from snakemd import Inline, Heading

>>> print(Heading("H3", 3))
### H3

>>> print(Inline("Word", bold=True))
**Word**

The Header change has a minor consequence to the Document API, which contains an add_header() method. This method has been renamed to reflect the name change to Heading.

Verification Code Has Been Replaced in Favor of Native Exceptions

At some point, I had this weird idea to include code in each of the elements that could be used to verify their correctness. This was a nightmare for a variety of reasons, but it was mainly a problem because so many elements had no need for verification. As a result, the Verification library no longer exists, which caused the removal of the verify() method from every element and the check_for_errors() method of document. Instead, if there are issues with the construction of an element, you’ll be notified by a carefully crafted Exception:

>>> from snakemd import Heading

>>> heading = Heading("H7", 7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\jerem\AppData\Local\Programs\Python\Python310\lib\site-packages\snakemd\elements.py", line 386, in __init__
    raise ValueError(
ValueError: Heading level must be between 1 and 6 but was 7

The API includes direct references to the applicable ValueErrors, so take a peek.

On top of the removal of the Verification class, various verify-related methods have been removed. Mainly, all of the code for verifying URLs. I felt that this code was flaky—as I don’t really understand networking—and somewhat out of the scope of a project that is just meant to generate Markdown.

And finally, speaking of networking, I tried to update any code mentioning URLs to use the term “link” instead. These seems a little more broad to allow for both URLs and paths.

Example Transitions

For the first time in my life, I’ve taken advantage of the pre-release versioning scheme for Python. As a result, I was able to release a handful of beta versions of the software to be sure everything was working as expected. As a result, I figured I’d share with you the links to the pull requests of those transition jobs:

With all that said, I hope you find the transition to v2.x as seamless as me. If not, feel free to reach out on DiscordOpens in a new tab.. Otherwise, take care!

SnakeMD News (3 Articles)—Series Navigation

SnakeMD is a Python package that allows users to generate Markdown.

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 and kid, playing Overwatch 2, Lethal Company, and Baldur's Gate 3, reading manga, watching Penguins hockey, and traveling the world.

Recent Code Posts