ZIP Modules
When a module grows past 300+ lines, keeping everything in one file becomes painful. A ZIP module is a packaged folder (package) with __init__.py and helper files.
my_module.zip
└── my_module/ <- root folder (name can be anything)
├── __init__.py <- entry point (required)
├── helpers.py <- helper functions
├── models.py <- models
└── services/
├── __init__.py
└── api.py <- API logic
How Does It Work?
- The bot extracts the ZIP to a temp folder
- Looks for the root directory with
__init__.py - AST-parses
__init__.py— checks forclass Meta - Copies the entire folder to
community/<name>/ - Registers the module
How Files Reference Each Other
In __init__.py you import classes and functions from other files the usual Python way:
# community/my_module/__init__.py
from ..core import loader, utils
# Import from your own files
from .helpers import format_result
from .models import MyPayload
from .services.api import fetch_data
class Meta:
name = "MyZIPModule"
description = "module from ZIP"
version = "1.0.0"
tags = ["example"]
dependencies = ["aiohttp"]
@loader.tds
class MyZIPModule(loader.Module):
@loader.command()
async def cmd(self, mx, event, payload: MyPayload): # MyPayload from models.py
data = await fetch_data(payload.text) # from services/api.py
result = format_result(data) # from helpers.py
await utils.answer(mx, result, event=event)
Other files:
# community/my_module/models.py
from pydantic import BaseModel, Field, model_validator, ConfigDict
class MyPayload(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
text: str = Field(min_length=1)
@model_validator(mode='before')
@classmethod
def parse(cls, v):
if isinstance(v, str):
return {"text": v}
return v
# community/my_module/helpers.py
def format_result(data: dict) -> str:
return f"<b>Result:</b> <code>{data.get('value')}</code>"
# community/my_module/services/api.py
from ..core import utils
async def fetch_data(query: str) -> dict:
return await utils.request(
f"https://api.example.com/search?q={query}",
return_type="json",
)
How to Build a ZIP
import zipfile
from pathlib import Path
def build_zip(source_dir: str, output: str):
name = Path(source_dir).name
with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as zf:
for path in Path(source_dir).rglob("*"):
if path.is_file():
zf.write(path, arcname=f"{name}/{path.relative_to(source_dir)}")
Or just archive the folder with any archiver as ZIP
init.py Structure
__init__.py is the entry point. It must contain:
1. class Meta — at module level
2. class XxxModule(loader.Module) — class with commands
3. Imports from your own files (if needed)
# __init__.py — minimal
from ..core import loader
from .logic import do_stuff
class Meta:
name = "Demo"
description = "..."
version = "1.0.0"
tags = ["demo"]
@loader.tds
class DemoModule(loader.Module):
@loader.command()
async def cmd(self, mx, event):
result = await do_stuff()
...
When ZIP vs When Single File?
| Situation | Format |
|---|---|
| Up to 200 lines | Single .py |
| 350-400+ lines | zip |
| Though of course, nobody's forcing you: you can write 3k lines in a single .py :D |
Important
__init__.pyis the entry point. It must containclass Metaand eitherclass XxxModule, or import the Module from another file in the package- All helper
.pyfiles are imported recursively - If there are subfolders — they also get imported (as submodules)
__init__.pydoes NOT have to containclass Module— it can be imported from another file in the package