Video summary
In this episode of Python Live Lessons, the focus shifts to documenting Python code effectively using docstrings within a project called Markdown Code Exec. The host explains that while a README file serves as a general introduction for users, docstrings provide specific, machine-readable documentation attached directly to functions and classes. These docstrings act as an intermediate layer between raw code and user-facing documentation; they are primarily intended for developers but can be automatically extracted to populate project documentation. The episode highlights two main approaches to generating documentation: using standard library utilities like `pydoc` or the popular third-party tool Sphinx, and a more lightweight method of manually extracting information from docstrings to build custom documentation sites.
To demonstrate a practical application, the host adds numpy-style docstrings to the `parse_text` and `parse_file` functions in the Markdown Code Exec project. These docstrings follow a semi-structured format that includes descriptions, parameter lists, and return value details, which can be parsed by tools like `numpydoc2md`. Instead of manually writing every section of the README, the host creates a template file containing placeholders for function references. By executing Python code within a silent Markdown block, the output of these docstrings is captured and inserted into the README template, automatically generating an up-to-date documentation file that reflects the current state of the source code without requiring manual edits.
The tutorial further explores how to document command-line usage by programmatically capturing the help text generated when running the script with the `--help` flag. Using the `subprocess` module, the host executes the Python interpreter to retrieve this help output as a string, which is then formatted and added to the README alongside the function references. This approach ensures that even command-line instructions are kept in sync with the codebase. The episode concludes by versioning the project, tagging it as 0.2.0 on GitHub, and emphasizing the concept of "bootstrapping" documentation—using the project itself to generate its own documentation. This method not only saves time but also ensures consistency between the code and its description, setting the stage for future episodes on publishing packages to PyPI.
Read the full video transcript
[Music]
hi everyone welcome to python live
lessons episode 5 the fifth episode
sixth
zero based indexing sixth episode of
python live lessons
um today we're going to pick up as as
every episode where we left off during
the last episode with our project our
python project markdown code exec
and today we're actually going to focus
on documenting the code so we're going
to add user documentation to the code so
that other people can actually use
a markdown code exec hi morpheus good to
see you again
um as always i'm doing my best to add a
little bit of interactivity in this this
live stream so if you have questions
don't hesitate to post them on the chat
and i will try to engage with that as as
much as possible
alright
good to see actually some returning some
returning small but but
loyal crowd so to say
so
what do we um
what do we have here when we talk about
uh yeah sorry got distracted there so
what what are we talking about when
we're talking about documenting python
code so
um
one way a very cheap way to document
your python code and we've already done
that to some extent is to add a readme
file to the to your source code right so
a little text document in our case in
markdown format to to explain how you
should use uh the the project in our
case markdown code exec
um and that's fine and it's also very
convenient because tools like github and
pi by often automatically take this
readme and make it very visible to to
the users or people who visit the
project site so that they can read that
so that's a low-hanging fruit and we're
actually going to use that also today
then we have so-called dog strings
and dog strings are a little bit of an
intermediate you could say between
regular
code commands in the source code
and documentation
so i would say comments in the source
code are mostly for either yourself or
other developers to understand how the
source code works
they are not really intended mostly for
users outside users to understand how to
use your code
a docstring is strictly speaking a
comment in the source code but you
attach it to a function in a way that i
will show you in a bit
and that helps you and other developers
to understand how the function should be
used but you can also automatically
extract the information from a docstring
and include it in the documentation for
the project such as a readme file so
that's what we're going to focus on
today sort of automatically generating
documentation from a dock stream
um
then there are some let's call them
frameworks or utilities that
automatically take a whole software
project
and generate sort of documentation from
it a pi doc is included in the python
standard library it's a utility that
generates documentation based on source
code it's not as far as i know used that
much
it's kind of outdated and not so popular
but it's included in the python standard
library
another utility that does the same is
called sphinx and sphynx is used a lot
and i would say
maybe other people disagree but i would
say that sphinx is actually the most
used documentation generator for python
projects if you go to if you sometimes
use the site readthedocs.io
on which a lot of documentation for
python projects is hosted that is
usually and perhaps even always
generated with swings
so what are strings and pi docs they're
basically just utilities that
automatically generate documentation
what we are going to do though is take
do it one step simpler we're not going
to generate the whole documentation site
that's overkill for our small project
instead what we're going to do is
generate a readme that contains
information that is automatically
extracted from the docstrings from our
functions and the cool thing is that
we're actually going to use our own
project markdown code exec to do so
right so that's a little bit of
inception right here for you because
we're going to actually use our own
project to document our own project
so before i actually go switch to the
editor what does numpy dock look like
here you have an example it's a bit
small for you to see maybe
but so what how it works issue here you
have a function definition summarize
and it's from a project time series test
that i developed
and then you see that just below the def
statement so just below the function
definition there is a bit of a comment a
string indicated by these triple quotes
and inside the string there's a
description that says well summarize
generates a string with human readable
summary blah blah but it also contains
some semi-structured
documentation so you see there's a
parameters which sort of underlines with
these hyphens and then it says that one
parameter is results colon it's a dict
and then there's a description of it
and then it says returns again
underlined with these hyphens that
should be a string
and
and you see that this is kind of
semi-structured documentation
and particular utilities such as numpy
dock
actually can read this these kinds of
strings and automatically interpret them
and turn them into pretty looking
documentation that's basically the idea
so let's switch to the to the code
editor
and again if you have questions or
suggestions about other ways to document
code don't be shy to to suggest them
up to presentation mode and no
there we go
so what i have here um i'm not going to
repeat again what our project does in a
lot of detail you'll see it in a bit in
action
um but basically this is the project
that we developed in previous episodes
and we have a folder markdown code exact
in it we have a submodule underscore
markdown code exec and in that sub
module we have two functions parse text
and parse file and those are the two
function that functions that we're
actually going to document
so the first step that i'm going to do
is add documentation to these
2d string to these functions in numpy
doc format
um and i'm being a bit late here
actually by doing this so normally i
would this is
now with the project is almost finished
and now i'm adding these dog strings
normally i would add dog strings earlier
on during the coding process just
so that i don't lose track of what i'm
doing essentially even though i'm maybe
not using them immediately to generate
documentation so i'm pretty big on using
doc strings myself
so how do you use docstrings well you
already saw it in the example you
basically start by having a triple line
string here
and everything in between will be a dog
string
so it's that simple so anything i type
here would be a dog string but i'm going
to
use a specific convention the
numpy.convention to actually make this
dog string in such a way that it is
readable by machine readable by by
utilities other utilities
so what does parstax do
takes a
str
off
containing markdown text
now text
so that's what it does right so here's
also the explanation for those of you
who don't know what the what the code
does it takes a string call line
markdown text
finds
code blocks in the markdown
up
in the markdown sorry
oh
and markdown
executes
these code blocks
and captures the output
and then
embeds
the captured
output in the markdown
um
maybe we can give an example of what
that would well actually let's say i go
i switch to demo.md just to make it a
bit more concrete for you what does this
do if we have a code block here in the
markdown the code block is indicated by
these triple triple blocks triple
backticks there are two print statements
that generate output of course what you
print then that output would be captured
and it would actually be inserted as
text below right and the idea would be
that that way you can illustrate
basically what code does when it is
executed that's the idea
so here the top level the the dog string
is basically a description
and then we're going to be say okay
there are parameters there's actually
only one parameters parameter but you
would call it parameters
um and it's called md
and it is a string that's what it is
the input
markdown string
and it returns something
up and i have to underline that as well
why well that's part part of the
markdown convention it's also a string
and then i would say the
let's call it the compiled markdown
string
that's it
this is the markdown numpy doc docstring
right and it's quite nice and it is
short and it describes what our function
does
now
below we have another function called
parse file
it is a little bit more of a higher
level function in the sense that it
actually takes an input file and an
output file but does more or less the
same thing as parse text
so
it takes
a markdown input file
processes you can you can be a little
bit better than i am right now in my cr
crafting a nice explanation of what the
function does right but i don't want to
spend too much time on that
parses executes
code blocks while capturing outputs
puts
and writes x
and writes the result
to a new output file
and then we have a source which is a
and what is it exactly exactly it is the
full path
to the input file
and we have a destination source our
string and it is the full path to the
output file
let's not do
that all right so now we have documented
our r uh our
um
we have documented our functions
now what are we going to do just
documenting the functions obviously
doesn't automatically generate generate
doesn't automatically generate
documentation but we can use this as a
start to generate documentation maybe i
should actually let's go you know up
just to kind of give you an idea of how
this works
so now i have my my terminal here at the
bottom
if i would say
import markdown
code exec
and then i would say markdown code exec
dot parse
file sorry parse text this thing here
above
then python will automatically use the
doc
give a underscore doc attribute to it
and that will contain the dog string and
you see how this basically allows other
utilities such as numpy dock to actually
take the dog string and turn it into
something nice uh nice right so
basically let me print it out and it
would look nicer
so up so basically that's the idea the
doc strings are automatically exposed
through this underscore underscore doc
underscore underscore attribute this by
the way works for functions but it will
also work for classes and other things
okay
now um what is the next step
the next step is that i'm i installed a
python library called
numpy.mp.2md you can install that just
with pip install pip install mp.2md
and what mp.2md does
is uh it parses these these dock strings
and generates nice documentation
so
how what what is the next step so here
we haven't read me the readme file that
we
created before
so
but this readme file has been hard coded
right everything that's inside it i
typed manually myself that's what i
don't want to do anymore i want to
create a template and i will call it
readme template.md
and in this readme template i want to
include information that is extracted
automatically
from the source code and then compile
that into the actual readme i hope that
that is a little bit clear right so i
want to sort of instead of manually
creating a readme i want to
automatically generate a readme based on
the based on the documentation in the
from the docs strings
so and i'm going to do this in the
following way i'm going to say okay here
i'm going to have a function reference
so underneath there's going to be
a function reference or a description of
the what what it looks like you know
what let me actually show you what it's
going to look like approximately that
will give you some better idea for what
i'm doing
yep
so
let me switch to a time
series test
this is a um
if you this is another project that i've
been working on and if i then scroll all
the way to the bottom you see this is a
nice project with lots of documentation
you see that here we have a function
reference
and it has some kind of nice description
it's not that pretty i think but it has
some kind of nice description of how how
you should use the functions from this
module and all of this has been
automatically extracted from these dog
strings from numpy dog formatted dog
strings so we want to do the same thing
basically for our
markdown code exact project
so that's what we're working towards
so how we're going to do that well we're
actually going to use markdown code exec
so i'm going to say well we have three
backticks and this contains python code
and this python code should be executed
silently
what does this mean again well if you
watch the previous episodes you may you
you may remember that basically
everything that is executed in a silent
code block
results in the output being captured
and then this the code block is replaced
by that output so what i'm going to do
here
is type some code that will basically
print out the dog strings
and then this this part right here will
be replaced by those dog strings in a
nicely formatted way that's the idea
kind of cool right and pretty fancy also
i think then i have to kind of have a
cheat sheet here because i need to
actually know a little bit what i'm
doing
um so the first function that we need to
know need to have is the functions that
we actually want to document
from markdown code exec
import
parse
up text and parse file those are the two
functions that we want to have
and we want to um
use numpy dock to md to actually parse
them
so from numpy mpdoc
yeah mpdoc
to md import and then there's a
render md
from object docs string i've i've seen
prettier function names i have to say
but basically it does it is quite clear
so this is a function that renders
markdown from the dock string of an
object
and then things are suddenly very simple
because we can say i want to print out
render
md
from object
docstring
and i parse pause parse text
and i call it
markdown
code exact.parse
text
so
this means i want to parse the docstring
from this function parse text
and then you have to specify for a
reason that's not entirely clear to me
what the function should be called maybe
in case you want to give it an alias i
don't so i just call it markdown code
exec parse text
this will print out that nicely
formatted dock string
then i will say up to new lines
and i will do the same thing for parse
file
parse file
oh now that's pretty cool right um
so let's see how we can actually execute
this
i hope the logic is clear if not
interrupt me with questions at any time
right
so the logic here is that everything
that is printed out in this code block
should actually be captured and the code
block itself should be replaced by that
so let's see how we can do that i will
switch to an editor here's or sorry
a terminal
and i will call python markdown code
exactly.pi
and i will say the input is the readme
template
and the output is the actual readme dot
md let's see what happens i hope this
works
you never know this is life after well
doesn't crash it's already a start
so let's switch back
and let's put them side by side
up
okay so here on the left we have the
template of the readme and here on the
right we have indeed the function
reference
it doesn't look very nice now right
because it is kind of like
marked down mixed with a little bit of
html etc etc
but that's okay it doesn't need to look
very nice like this because it will look
very nice once we actually upload this
to github
and then you will see how it works and
how nice it looks and you know what
we're just going to do that let's
actually post upload this to github so
you can see
i start get gui right so two lectures
ago i think we saw how git works and i'm
just doing the same if you're not
familiar with how git works watch back
that lecture
so what did i change here we have to
unstage changes
i added some dog strings that's what you
see here up
add dog strings
that's one commit
i created a readme template and a readme
and readme
and machine
generated readme build md
i commit this as well right so what i've
done now is i've basically frozen these
changes the unstaged changes were the
things that were changed but git didn't
know about it then i made a commit that
sort of locked these changes and i gave
them a name with this commit message
and then if i say push it will actually
upload all of these changes to github
where our project lives and that we
talked about two episodes ago
so that's done
close this switch back to the
github and now if we go not to time
series test but to
mark
code exec
we will see
that here we have our nice function
reference right for parse text
and for parse file
pretty simple pretty straightforward
pretty nice looking
so this is part of what i wanted to do i
guess the main main thing main nice
thing
is already that now we explained to our
users how they can actually use our
library programmatically by calling
these two functions however
um
you can
we the last app previous episode
actually we used to turn our python
module into a command line application
so that you don't actually need to say
from you know markdown code exec import
blah blah but you can just execute it on
the command line which by the way is
what i just did right when i converted
to readme and this is not documented
here yet
and i also want to document that and i
also because i hate doing things
manually and copy pasting i also want to
do that programmatically so how can i do
that
so let's go back
so let's say
uh command line
command line usage
and now we want to do more or less the
same
we want to do something here
dot python silent
that will actually result in the
explanation of the command line usage
being printed out
and this is a bit finicky i have to say
but i will show you how it works anyway
so the general idea is if i call python
up
if i call python markdown
python markdown code exec
with
a help right like this it will actually
print out a simple help for how you can
use this on the command line
and i want to actually embed this this
information
in our
in the in the in the markdown that's
what i want to do
but as i said that's a little bit
finicky so let me show you how that
works because what it involves
is calling my python markdown code exec
as a command as a command line app from
python and that's a bit finicky but we
can do it so i'll walk you through it
okay let's close this
and i have my cheat sheet because i
wouldn't be able to do this from the top
of my head
so the first thing that we need is
subprocess subprocess is a python module
that executes stuff
that basically allows you to execute
things
then we can say sub process dot run
and subprocess dot run takes a list of
things that should be executed so that
not on the terminal you would type
python space
markdown code etc here you wouldn't use
spaces but you would pass a list of the
separate words so it would go like this
python
markdown code exec
dot pi
up
help
all right that's the first thing
then we need to do a few other things we
need to say we want to capture the
output otherwise it will go lost
and we want it
this is text so we want
this is a bit of a technical detail but
we don't want this to be bytes but we
want this to be actual
unicode text objects that we're
capturing
and that's it essentially
although if i were to execute that i
know because i tried it out
this would crash because python doesn't
know how to fight python which is a bit
strange so just calling python like this
wouldn't work
but we can say instead of just python we
can say systoled executable then you ask
what is systoled executable well i will
show you
import sys
system executable
this is the
full path to the python interpreter so
if you are writing a python script and
you're wondering where is the python
interpreter that was actually used to
buy execute this script this is
sys.executable
so what i'm basically saying here i know
at least one python interpreter where it
lives namely systoled executable and i'm
going to use that to execute markdown
code exec pass the argument help
capture all the output
i want this should be text which is a
bit superfluous to me
and then
that will return a process object which
i assign to p
and i know
p dot std out will be contain all the
output that was generated during the
execution of that code
let's see whether this actually works if
i run like this up
check
uh invalid syntax why is this oh yeah
comma
run trip you see yeah this actually
works right so if in rapunzel i click
quick run it will detect actually that
you want to run everything between the
back ticks and it will be capturing it
all right
so this looks pretty good
to me so what i can do now is again
switch back to the terminal and do more
or less what i did before up
take the readme template as input the
readme is output up
and if i now switch back to rapunzel
let's take a look at what the output or
what the actual readme log looks like
all right
and now you see in the readme we also
have for the command line usage a bit of
an explanation and our function
reference of course still
oof well that's about it right so
there's lots of room for improvement
here but at this point it's mostly
aesthetics i think right making it look
a little bit nicer adding a bit of a
nicer
descriptions to the dog strings adding a
little bit more information to the
readme file but in principle now we have
a very sweet way to automatically
generate an up-to-date readme file
avoiding us from having to manually
change the readme all the time right
that is kind of the advantage that we
have here
um yeah
so at this point what i want to do
i think this is a nice moment also to
say okay
up in the unit
i think we are pretty fancy now i think
we're going to bump to version 0.2.0
um i'm going to commit the code
tag it as version 0.2.0
and um then we have a very nice new
release of markdown code exec
so uh yeah right let's start get gooey
again we have a few changes right but
now they're mostly limited to the to the
readme update read me
up
oh wait actually this should also be
and i i tend to say usually if i change
the version i just change the version
and the thing and i say bump to 0.2.0
uh i push this up to the master so that
we have an up-to-date thing
on github i'm doing this a bit quick
because we've
covered all these steps in previous
episodes
and now i'm also going to tag a release
how does that work again git
so i just use the git from the command
line now i say i want to tag release 0
2.0
all right
this this slash the only reason i'm
typing it is because otherwise the line
is a bit long right it's just the
carriage return
and now we say git push origin release
0.2.0
so i push the tag to github as well
resulting this from appearing on github
there we go
switch back
reload the page
and we have a fresh uh up to updated
markdown code exact right here
right you see here this is the command
line usage isn't that nice the way it is
printed out here so you can use a little
bit of aesthetic tweaking but other than
that all the crucial information is here
and i think the nice thing also is that
in this kind of inception kind of way
we've used markdown code exact to
generate its own documentation it's
bootstrapping of documentation of sorts
and i will actually use markdown code
exec myself quite heavily in other
projects that's the main reason that i
started this to begin with because i
thought it would be nice to share with
you all during python life lessons but
also because i actually want to use it
um
and
that's it what i wanted to cover today
next week i hope you will be back and
then we're going to i think right now we
have a nice package it is time to upload
it to pi pi to actually make a nice
release that other people can pip
install so i hope you will tune in then
to see how you can actually publish
things on pipeline
um if you're watching this back the
recording
then this is where python live lessons
ends thank you for tuning in if you're
watching live then if there are any few
questions or comments then i'm still
more than happy to take them
[Music]