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
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.
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 PyPI, 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:
- Converted all code snippets in docs to doctests
- Reworked string input for
Quoteto pass directly through raw
- Updated language around parameters in documentation to provide a list of possible inputs and their effects
- Removed several deprecated items:
verify()from the entire repository
- Replaced several deprecated items:
- Added several new features:
- Included a
Quoteblock which allows for quote nesting
ValueErrorexceptions in various class constructors
- Started a resources page in documentation
- Created a requirements file at the root of the repo to aid in development
- Included a
- Improved various aspects of the repo:
- Expanded testing to 163 tests for 100% coverage
- Clarified design of
Inlineto 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
- Removed several deprecated items:
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.
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
>>> 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.
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:
- Updated Requirements to SnakeMD 2.0.0b1 by jrg94
- Upgrade to SnakeMD 2.0.0b1 by jrg94
- Converted SnakeMD Dependency to 2.0.0b1 by jrg94
- Updated SnakeMD Version to 2.0.0b2 by jrg94
- Updated Library to Latest Version of SnakeMD (2.0.0b2) by jrg94
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 Discord. Otherwise, take care!
Recent Code Posts
Python software should always be versioned, but it's not always clear how. Luckily, I've shared some of my experience with versioning here.
Why Is Adding Two Random Numbers Not the Same as Generating One in the Same Range?
Generating random numbers might seem easy at first, but there are definitely some pitfalls.