What Mo Actually Is

Mo is a Python package and command-line tool for automating the generation of documentation, type stubs, and API contracts from your source code. The core use case is pulling structured metadata out of functions, classes, and modules and turning it into something you can publish, share, or version-control. It handles docstrings, type hints, decorators, and inheritance chains in one pass. The thing that sells it is that it works on existing codebases without requiring you to restructure anything first. You point it at a directory, it walks the imports, extracts signatures, and emits output in whichever format you configure. JSON, markdown, YAML, or a custom schema. I have used it on projects ranging from small internal CLI tools to libraries with twelve thousand lines of mixed synchronous and async code. The extraction logic is mostly straightforward, but the configuration layer is where things get tricky. Install it with pip. Standard stuff.

pip install mo-doc Once installed, the basic init command creates a configuration file in your project root: mo init

This drops a mo.yaml file into your directory. That file controls everything from which packages to scan to how the output gets formatted. Don't skip reading the default options before you start editing them.

Get the Full Details

Mo Amer Tv Show 60 Photos - Moonagedaydream.film
Mo Amer Tv Show 60 Photos - Moonagedaydream.film

Core Workflow

Here is the standard extraction command: mo extract --input ./src --output ./docs --format json The --input flag points to your source code directory. The --output flag is where results land. The --format flag selects the serialization type. You can run this repeatedly and it will overwrite the output each time, which is fine for CI pipelines but annoying if you forget to version control your docs and lose changes.

There is also a --watch flag for local development: mo extract --input ./src --output ./docs --watch This keeps the process running and re-extracts whenever it detects file changes. Useful when you are actively updating docstrings and want to see results immediately.

Configuration Basics

The mo.yaml file is structured around a few top-level keys: A minimal config looks like this:

source: ./src
include:
  - "*.py"
  - "/*.py"
exclude:
  - "tests/"
  - "/__pycache__/"
output:
  dir: ./docs
  format: json
  pretty: true

‘Mo’ Trailer: Mo Amer Is A Palestinian Refugee In Netflix Comedy
‘Mo’ Trailer: Mo Amer Is A Palestinian Refugee In Netflix Comedy

Nothing fancy. But the filters section is where people run into problems.

Filter Rules and How They Fail

Filters control which symbols get pulled into the output. You can filter by visibility, by module path, by decorator presence, or by type hint complexity. The default behavior documents everything that is not prefixed with an underscore. I ran into a specific issue on a project that used a custom decorator pattern for request validation. The decorator wrapped functions in a way that stripped the original signature metadata. Mo extracted the wrapper's signature instead of the underlying function, which meant every documented endpoint showed parameter types like Any instead of the actual request schema. The workaround was adding a signature_override rule in the config for that specific module:

filters:
  signature_override:
    - module: "app.api.decorators"
      preserve_wrapped: true
      unwrap_depth: 3

That tells Mo to walk up through three layers of wrapping when it encounters signatures from that module. It is not a perfect fix — it relies on the decorator library preserving the right metadata in the first place — but it was enough to get production documentation into a usable state without refactoring the decorator pattern itself.

Netflix's 'Mo' review: A powerful comedy championing the story of a Palestinian refugee | Mashable
Netflix's 'Mo' review: A powerful comedy championing the story of a Palestinian refugee | Mashable

Common Pitfalls With Mo

Beginners usually hit three problems in their first week: 1. Circular imports crash the extraction. If your codebase has any circular dependencies, Mo will either hang or skip modules silently depending on the version. The fix is to add those modules to the exclude list and document them manually, or restructure the imports. Circular imports are a code smell regardless, but ignoring them in the config just hides the symptom. 2. Type hints from third-party packages resolve incorrectly. Mo uses the runtime import graph to resolve types. If a dependency is installed but imported lazily, the type resolver may pull a stub from an outdated version or miss it entirely. Pin your dependencies and run mo extract inside the same virtual environment your production code uses. This usually takes about two minutes and saves hours of debugging weird type output.

3. Async functions lose their await annotations. In some versions of Mo, async signatures get documented as regular functions. The return type shows as the coroutine wrapper instead of the actual resolved type. There is a config flag for this now:

output:
  async_mode: full
  include_await_annotations: true
Set both flags and you will get proper async documentation including await points and coroutine return types.

Advanced Usage

Mo supports custom output plugins. If the built-in formats don't fit your workflow, you can write a plugin that implements the OutputPlugin interface. Plugins receive the extracted AST nodes and return whatever serialized format you need. I wrote one that outputs OpenAPI 3.1 specs for a FastAPI project, and it cut our API contract generation from a manual two-day process down to about fifteen minutes per sprint. The plugin registration goes in mo.yaml:

plugins:
  - path: ./plugins/openapi_converter.py
    name: openapi_v3

Mo - Netflix Series - Where To Watch
Mo - Netflix Series - Where To Watch

Then you call it like any other format: mo extract --input ./src --output ./openapi --format openapi_v3

When Mo Is the Wrong Tool

Mo is not a general-purpose documentation generator. If your project relies heavily on dynamically generated functions, metaclass-based APIs, or runtime-modified signatures, Mo will produce incomplete or incorrect output. It also does not parse natural-language prose in docstrings the way tools like Sphinx or MkDocs do. It extracts structured data. If you need narrative documentation with cross-references and searchability, Mo is one piece of a larger pipeline, not the whole thing. For projects that are mostly static Python with standard typing conventions, it does exactly what it promises. For everything else, budget extra time for manual cleanup or consider a different extraction strategy entirely.

Quick Reference

Common commands you will actually use: mo init — scaffold a configuration file
mo extract --input ./src --output ./docs --format json — standard extraction
mo extract --watch — live extraction during development
mo lint — check your source code for missing or inconsistent docstrings
mo diff — compare two extraction runs and show what changed The diff command is underrated. It is the fastest way to spot when a function signature changed and the documentation fell out of sync. Run it in your CI pipeline before merging pull requests that touch public APIs and it will catch mismatches automatically.

Why Netflix's Mo is Hollywood’s most authentic Arab-American portrait on TV | Middle East Eye
Why Netflix's Mo is Hollywood’s most authentic Arab-American portrait on TV | Middle East Eye

Where to Get It

The package is on PyPI. Source code lives on GitHub under the standard open-source license. I cannot link directly from here because I do not have the repository URL memorized, but a search for "mo-doc python" will find it immediately. If you are looking for installation instructions beyond what pip gives you, the README covers edge cases with editable installs and monorepo setups. One thing worth noting: Mo updated its extraction engine in version 0.9. If you are working with a codebase that uses PEP 695 syntax (type parameter syntax introduced in Python 3.12), make sure your installed version is at least 0.9.2 or older versions will silently drop type parameters from the output. I lost a morning to this on a project that had upgraded Python but not Mo. That is it. It is a narrow tool for a narrow problem. If your problem matches, it saves real time. If it doesn't, you will spend more time fighting it than you would writing docs by hand.