Skip to main content

Async minecraft modpack resolver and downloader

Project description

packlayer

Resolves and installs Minecraft modpacks from Modrinth slugs, FTB IDs, direct URLs, or local .mrpack files.
One call. No launcher required.

Python PyPI License Platform


What it does

You give it a modpack source — a Modrinth slug, an FTB pack ID, a direct .mrpack URL, or a local file — and it resolves the manifest, downloads all mod files concurrently, verifies their integrity, and drops them into a folder. There's a CLI for one-off use and a full async Python API for integration.

The resolver is provider-agnostic by design. Built-in support covers Modrinth and FTB. Additional providers can be plugged in without touching the library.


Installation

pip install packlayer

Requirements: Python 3.14+


Usage

CLI

packlayer install mr:fabulously-optimized
packlayer install https://modrinth.com/modpack/fabulously-optimized
packlayer install ftb:79
packlayer install ./mypack.mrpack
packlayer install mr:fabulously-optimized --minecraft 1.20.1 --dest ./dest

All options

Flag Description
--dest <path> Output directory (default: ./dest)
--version <version> Pin a specific modpack version (e.g. 6.0.1)
--minecraft <version> Filter by Minecraft version (e.g. 1.20.1)
--side <client|server|both> Which side to install for (default: client)
--no-optional Skip optional mods
-v, --verbose Enable debug logging

Python API

One-shot

import asyncio
from packlayer import install_modpack

asyncio.run(install_modpack("mr:fabulously-optimized", "./mods"))

Client

import asyncio
from packlayer import PacklayerClient

async def main():
    async with PacklayerClient(minecraft_version="1.20.1") as client:
        versions = await client.list_versions("mr:fabulously-optimized")
        modpack  = await client.resolve("mr:fabulously-optimized", modpack_version=versions[0].version_number)
        results  = await client.install(modpack, "./mods")
        print(f"{results.total} files installed")

asyncio.run(main())

Progress tracking

from packlayer import PacklayerClient

async def main():
    async with PacklayerClient() as client:
        modpack = await client.resolve("mr:fabulously-optimized")

        def on_start(total: int) -> None:
            print(f"downloading {total} files")

        def on_progress() -> None:
            print(".", end="", flush=True)

        await client.install(
            modpack, "./mods",
            on_start=on_start,
            on_progress=on_progress,
        )

on_progress is called once per installed file (mods and overrides). Both sync and async callables are accepted.

Install options

from packlayer import PacklayerClient, InstallOptions

async def main():
    async with PacklayerClient() as client:
        modpack = await client.resolve("ftb:79")
        await client.install(
            modpack, "./mods",
            options=InstallOptions(
                side="server",
                include_optional=False,
            ),
        )

Reference

install_modpack

async def install_modpack(
    source: str,
    dest: str | PathLike[str],
    *,
    minecraft_version: str | None = None,
    modpack_version: str | None = None,
    concurrency: int = 8,
    on_start: Callable[[int], None] | None = None,
    on_progress: ProgressCallback | None = None,
    options: InstallOptions | None = None,
    extra_resolvers: list[ModpackResolver] | None = None,
    default_resolver: ModpackResolver | None = None,
) -> InstallResult
Parameter Description
source Local path, direct URL, mr:<slug>, Modrinth project URL, or ftb:<id>
dest Destination directory. Created if it does not exist
minecraft_version Filter versions by Minecraft version (e.g. "1.20.1")
modpack_version Pin a specific modpack version (e.g. "6.0.1"). Uses latest if omitted
concurrency Max simultaneous downloads. Default: 8
on_start Callback invoked with the total file count before downloading starts
on_progress Callback invoked after each installed file (sync or async, no arguments)
options Controls which files are installed. See InstallOptions
extra_resolvers Additional resolvers registered before built-ins
default_resolver Fallback resolver when no registered resolver claims the source

PacklayerClient

class PacklayerClient:
    def __init__(
        self,
        *,
        minecraft_version: str | None = None,
        concurrency: int = 8,
        extra_resolvers: list[ModpackResolver] | None = None,
        default_resolver: ModpackResolver | None = None,
        config: PacklayerConfig | None = None,
    ) -> None

Must be used as an async context manager.

Method Description
resolve(source, *, modpack_version) Resolve a modpack without downloading files
install(modpack, dest, *, on_start, on_progress, options) Install a resolved modpack to disk
list_versions(source) Return available versions, newest-first
resolver_for(source) Return the resolver that would handle source
resolvers() Return all registered resolvers in priority order

Models

Modpack

Field Type Description
name str Display name
version str Version string
minecraft_version str Target Minecraft version
files tuple[ModFile, ...] Mod files to be downloaded
overrides tuple[Override, ...] Non-mod files to be written into the instance directory

ModFile

Field Type Description
url str Download URL
filename str Bare filename
size int Expected file size in bytes
hash str | None Hex digest, verified post-download if provided
hash_type "sha512" | "sha1" | None Algorithm used for hash
optional bool Whether the file is optional
side "client" | "server" | "both" Which side this file targets

Override

Field Type Description
path str Destination path relative to the instance root (e.g. "config/sodium.json")
side "client" | "server" | "both" Which side this override applies to
data bytes | None Raw file contents bundled inline (e.g. from .mrpack zips). Mutually exclusive with url
url str | None Remote URL to fetch the file from (e.g. FTB overrides). Mutually exclusive with data

ModpackVersion

Field Type Description
id str Provider-assigned version ID
version_number str Human-readable version string
name str Release display name
loaders tuple[str, ...] Supported mod loaders
game_versions tuple[str, ...] Compatible Minecraft versions
date_published str ISO 8601 publish timestamp

InstallOptions

Field Type Default Description
include_optional bool True If False, optional files are skipped
side "client" | "server" | "both" "client" Files incompatible with this side are skipped

Exceptions

All exceptions inherit from PacklayerError.

Exception Description
LocalFileNotFound Local path does not exist
InvalidMrpack File is not a valid .mrpack archive
SlugNotFound No project matches the given slug or ID
NoVersionFound Project exists but has no compatible version
NoResolverFound No registered resolver claimed the source
HashMismatch Hash digest mismatch after download
NetworkError Network failure

Supported providers

Provider Source format Auth required
Modrinth mr:<slug>, Modrinth project URL, direct .mrpack URL, local .mrpack No
FTB ftb:<id>, feed-the-beast.com URL No

Plugin system

packlayer dispatches resolution to a registry of ModpackResolver instances. extra_resolvers are registered before built-ins, giving them higher priority.

Implementing a resolver

from packlayer.interfaces.resolver import ModpackResolver
from packlayer.domain.models import Modpack, ModpackVersion

class MyResolver(ModpackResolver):
    def can_handle(self, source: str) -> bool:
        return "myprovider.com" in source

    async def resolve(self, source: str, *, modpack_version: str | None = None) -> Modpack:
        ...

    async def fetch_versions(self, source: str) -> list[ModpackVersion]:
        ...

can_handle must be exclusive — return True only for sources this resolver definitively owns.

Registering

async with PacklayerClient(extra_resolvers=[MyResolver()]) as client:
    modpack = await client.resolve("https://myprovider.com/modpacks/mypack")
    await client.install(modpack, "./mods")

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

packlayer-0.1.0.tar.gz (76.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

packlayer-0.1.0-py3-none-any.whl (31.3 kB view details)

Uploaded Python 3

File details

Details for the file packlayer-0.1.0.tar.gz.

File metadata

  • Download URL: packlayer-0.1.0.tar.gz
  • Upload date:
  • Size: 76.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for packlayer-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1a178c7058accefbac5718c01f26daf6d0c2c21974eaa54a00650767222792c1
MD5 f741bbb323079a9a11a5cb445682f231
BLAKE2b-256 2336fbf201f54a850e36236667cfc6a0acbd2cf6f1d933140db313ac4edd6e33

See more details on using hashes here.

File details

Details for the file packlayer-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: packlayer-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for packlayer-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4560fcea36cf939dd6233cbf5355ff7e45e2d19de117d064f0b68c8521e1bc3b
MD5 0f6fdc04b3be8ccf9d91297789b9b7a1
BLAKE2b-256 9321cb885187997396b7554618c027b1b37063218fa9d084e225fa399cdd059e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page