Skip to content

PyMoe

Python Versions Version PyPi Github

Welcome to PyMoe. PyMoe is a general purpose Python library that wraps APIs for several large websites that offer information about Anime, Manga, Light Novels, and Web Novels. Currently six different APIs are supported with plans for more in the future (Especially looking at MangaDex integration).

  • Supports Several APIs


  • Simple and Expressive


    This library ships with custom API aware iterators. Every function that returns multiple results returns a generator. This simplifies code and means that this is a valid code segment.

    for item in pymoe.anime.search.shows("Toradora"):
        print(item)
    

Getting Started

Versioning

Keep in mind that PyMoe currently has a legacy version. The legacy version is 1.0.6 and exists only to not break applications written under that version. It is vastly different from the current version which is 2.*. The biggest difference is the unification of the internal API.

Installing PyMoe is easy. You have several options depending on your needs, though the recommended path is to use a project manager like Poetry or Hatch. Barring that, you can install it using pipx which automatically sandboxes your pip into isolated environments. You can install it using the pip shipped with your python installation, however this is not the recommended path as you typically want your primary python environment to remain clean.

  • pipx


    pipx install pymoe
    

    Or to install legacy...

    pipx install pymoe==1.0.6
    

  • Poetry

    In pyproject.toml


    pymoe = "*"
    

    Or to prevent unnecessary breakage...

    pymoe = "2.2.*"
    

    Or to install legacy...

    pymoe = "1.0.6"
    

  • Hatch

    In pyproject.toml


    dependencies = [
        "pymoe"
    ]
    

    Or to prevent unnecessary breakage...

    dependencies = [
        "pymoe==2.2.*"
    ]
    

    Or to install legacy...

    dependencies = [
        "pymoe==1.0.6"
    ]
    

  • The Not Recommended Way


    pip install pymoe
    

    Or to install legacy...

    pip install pymoe==1.0.6
    

Further Reading

Now that you have PyMoe properly installed and linked, you'll want to get using it. Depending on your level of experience in python, the documentation is spread out across several files written in different ways.

  • If you are new to the library, I would recommend starting at the User Guide. This covers basic usage across all of the library and more specific details for each implementation.

  • If you just want a quick overview of the library then please read the section below.

  • If you instead want to see code documentation, you can either see Github or read the autogenerated documentation at Autogenerated.

  • Finally, if you want to build PyMoe from source, then take a look at Building which covers how the build pipeline is setup for this project.

Overview

Once installed, the basic operation of the library is to import pymoe and then use the modules you need. You can also just import specific modules if you only need one data type. There are a few quick examples below.

Default Providers

It's important that you are aware of the default providers across each module. You can of course use only the specific service you want, but these are the services used when you use the top level functions in pymoe.type.operation.endpoint. These providers are Anilist for Anime and Manga and WLNUpdates for LN.


Importing

# The whole thing
# All the examples are written around this kind of import
import pymoe

# Custom named import
import pymoe as owu

# Just the anime module
from pymoe import anime

# This one is probably not so useful, but it only allows you to use anilist get endpoints
from pymoe.anime.get import anilist

# For when you really don't want to keep typing pymoe.anime.search
pyref = pymoe.anime.search
# Now we can just...
pyref.shows("Toradora")

# Or you could do say
from pymoe import anime as pyani
# And then just...
pyani.search.shows("Toradora")

Basic Usage - Searching

import pymoe

# Print information about each item returned in the search
for item in pymoe.anime.search.shows("Toradora"):
    print(item)

# Maybe you want manga instead
for item in pymoe.manga.search.manga("Toradora"):
    print(item)

# Now you want novels?
for item in pymoe.ln.search.series("Toradora"):
    print(item)

Basic Usage - Getting items by ID


MangaUpdates IDs

A quick warning about the IDs for MangaUpdates. If you were to search for the series Slam Dunk on their website you would end up at https://www.mangaupdates.com/series/pklldyu/slam-dunk. However, do not be fooled. pklldyu is NOT the ID you use with the API. This is a Base36Encoded ID used for the website. You'll need to decode the ID to 55665151734 which is the proper ID for Slam Dunk in the API. There is a helper function for this at pymoe.utils.helpers.base36Decode. You can also just call int(string, 36) from your code.

import pymoe

# Print information about Anilist Show ID 154587
# This show is Frieren: Beyond Journey’s End
print(pymoe.anime.get.show(154587))

# For Manga information from Anilist Manga ID 97722
# This manga is Yami ni Hau Mono Lovecraft Kessakushuu
print(pymoe.manga.get.manga(97722))

# For LN information from WLNUpdates
# This is the novel Death March kara Hajimaru Isekai Kyusoukyoku
print(pymoe.ln.get.series(581))

# To reinforce the warning, this is the series Slam Dunk from MangaUpdates
print(pymoe.manga.get.mangaupdates.series(55665151734))

Advanced Usage - Specific Services

If you want to use specific services then you can call them as pymoe.type.operation.service.endpoint. For example, pymoe.ln.search.wlnupdates.series.

import pymoe

# Specifically Anilist
# This is Frieren: Beyond Journey's End as above
pymoe.anime.get.anilist.show(154587)

# Specifically kitsu
# This is also Frieren: Beyond Journey's End
pymoe.anime.get.kitsu.show(46474)

The services available for each type are listed below.

A Note About Kitsu

You should know that Kitsu is not supported long term. They intend to redo their API and move to Algolia for searching. Algolia has their own libraries and requires developer registration on their website to use the search endpoints. GET endpoints should continue to work as they already do, however there will come a point when this library no longer supports searching at Kitsu. I have no intention of removing GET endpoints as long as they continue to work.

Type Services
ANIME Anilist, MAL, Kitsu
MANGA Anilist, Kitsu, MangaUpdates
LN Bakatsuki, WLNUpdates

Expert Usage - Just Some Pattern Discussion

This section is optional. It goes over a few notes that developers might be interested in and the ways I would work around the library.

If I didn't want to keep typing pymoe.type.operation.service.endpoint I would probably...

import pymoe

# Make a Tuple
ANILIST = (pymoe.anime.get.anilist, pymoe.anime.search.anilist)
# Then I can just
ANILIST[0].show(154587)

# Or Maybe Make a dictionary
ANILIST = {
    'get': pymoe.anime.get.anilist,
    'search': pymoe.anime.search.anilist
}
# Then I can just
ANILIST['get'].show(154587)

User-Agents

There is no easy way to change the User-Agent used across the entire library. Each module has its own settings and by extension its own User-Agents. This means you can manually control the User-Agent sent to each service, but does also mean there's more work upfront to change them.

If I wanted to change the user-agent, then I would...

import pymoe

# This example is the same for all the modules
# Let's make our tuple again
ANILIST = (pymoe.anime.get.anilist, pymoe.anime.search.anilist)

# Now change our user-agent
for x in ANILIST:
    x.settings['header']['User-Agent'] = "My Cool App (github.com/mysupername/mycoolapp"

# For another extra example, let's do MangaUpdates
MANGAU = (pymoe.manga.get.mangaupdates, pymoe.manga.search.mangaupdates)
for x in MANGAU:
    x.settings['header']['User-Agent'] = "My Cool App (github.com/mysupername/mycoolapp"

If you need to see how different services return different data, then I would take a look at the testing suites under tests. I intentionally extract the data out as needed both for ease of code writing and also to show how the data is nested on return.

Rate Limits

While this library does not directly manage Rate Limiting, I do try to give you all the tools to do so on your own. First, let's go over how each service handles rate limiting.

Service Rate Limiting Methods
ANILIST Headers and 429s, 90 Requests per Minute
MYANIMELIST No Documentation, May Not Even Exist
KITSU No Documentation
WLNUPDATES JSON Object. 'Error' is True and 'Message' has rate limited
MANGAUPDATES 412 for Rate Limited, 429 for Cloudflare
BAKATSUKI ratelimited in return text

At the moment, my intention is to add returning headers along with the requested data into the library. However, any method I might use to do that would be a breaking change so this will likely not happen until a 3.0 version comes around. For now, you can check for status code 429 in the ServerError raised when Anilist encounters a 429. You can do the same for MangaUpdates which returns 412 for Rate Limiting or 429 for Cloudflare blocked. Bakatsuki and WLNUpdates are a bit easier. You can simply search for the phrase 'ratelimited' or 'rate limited' in the return text. As for MyAnimeList or Kitsu, there's absolutely no information I can give you, but if you ever do hit some kind of rate limit or 429 from one of those services, feel free to open up an issue at the github so I can add it to the documentation and plan to react to it in the future.

For now, my overall suggestion would be to add short breaks between each call if you're doing batch processes. Even just 0.5 seconds will likely prevent you from hitting a rate limit. Here is an example of both catching the error and a slight pause between requests.

import time
import pymoe

result = None

try:
    result = pymoe.manga.get.manga(30002)
except pymoe.utils.errors.serverError as ex:
    # Assuming a 429, this would print: Failed. Got 429.
    print("Failed. Got {}.".format(ex.code))
else:
    print(result)

# For a slight pause, assuming results is a list of character IDs
for item in results:
    print(pymoe.manga.get.character(item))
    # DO NOT DO THIS IN AN ASYNC LOOP. 
    # USE ASYNCIO.TIME OR THE EQUIVALENT FOR YOUR PARTICULAR LOOP ENGINE.
    time.sleep(0.5)

Asynchronous Operation

This library wasn't specifically built for asynchronous work though I would welcome a push request to a new branch. That being said, this code doesn't do anything that would be thread unsafe. It is literally a wrapper around web requests. So you can absolutely throw it into asynch code using threads or asyncio.executor and it shouldn't do anything that will mess you up.