Creating a repository¶
Warning
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
Let’s write a simple repository with just one module. We’re gonna name it bistro, because we’ll gonna be making some delicious snacks.
You can start with an empty directory (named lemon-bistro, for example), or you can use our template repository, it doesn’t matter.
Repository metadata¶
The first file we’re gonna create will be __init__.py. This file MUST be present in your repository, because lemon.py reads its information in order to work with it. It has to contain three variables described below´:
__name__is a string representing name of the repository. It must be instance-unique and can only contain lowercase ASCII letters and a dash ([a-z_]+) and MUST NOT becoreorbase. Moderators can run the repository list command to show installed repositories to prevent name clashes.__version__is a string that MUST follow the semver rules.__all__is a tuple of strings that lists all modules included in the repository.
In our case, the file might look like this:
__name__ = "bistro"
__version__ = "0.0.1"
__all__ = ("bistro", )
Next file that SHOULD be present in your repository is README.md or README.rst. This file should contain the information about the repository and its modules. It should also link to the lemon.py project, so the visitors aren’t confused about the meaning of it.
Our README may start like this:
# Bistro
An unofficial [lemon.py](https://github.com/Pumpkin-py) extension.
The module allows you to ...
The last metadata file is CHANGELOG.rst, which SHOULD be present in your repository. For each version you create you MUST add a second-level heading with a version number and a text content, which SHOULD be a list of points describing the changes in the version.
CHANGELOG
=========
Unreleased
----------
- Fix for #14: Don't allow infinite cakes
0.0.2
-----
- Add dynamic prices
- Add option to close the bistro
0.0.1
-----
- Initial release
Resource files¶
requiremens.txt MAY be present in the repository. If found, the lemon.py instance will use standard tools to install packages from this file. You MUST NOT add packages your modules do not require.
Note
Use requiremens-dev.txt for development packages.
The module¶
For each module that has been specified in the init’s __all__ variable there must exist a directory with the same name at the root of the repository. And each module has to have a module.py file inside of its directory.
In our case, we only have one module specified, so we have to create a file bistro/module.py. This file has to contain the class inheriting from guilded.py’s Cog and the setup() function to load the module.
import guilded
from guilded.ext import commands
from core import acl, text, logging
tr = text.Translator(__file__).translate
bot_log = logging.Bot.logger()
guild_log = logging.Guild.logger()
class Bistro(commands.Cog):
def __init__(self, bot):
self.bot = bot
...
def setup(bot) -> None:
bot.add_cog(Bistro(bot))
Note
See subarticles on logging and text translation.
Module database¶
When the module uses database in any way, the SQLAlchemy tables MUST be placed in <module>/database.py.
How the table class is named is up to you; the __tablename__ property SHOULD be named <repository>_<module>_<functionality>. Each entry SHOULD have primary index column named idx. Each table SHOULD have a guild_id column, unless you have reason not to do otherwise – so the data from multiple guilds don’t clash together.
Channel column SHOULD be named channel_id, message column SHOULD be named message_id, user/member column SHOULD be named user_id – unless there is a situation where this is not applicable (e.g. two user colums).
All database tables SHOULD have a __repr__ representation and SHOULD have a dump function returning a dictionary. Database operations (get, add, remove) SHOULD be implemented as @staticmethods.
Note
Always use remove() over delete(), for consinstency reasons.
An example database file bistro/database.py may look like this:
from __future__ import annotations
from typing import Optional
from sqlalchemy import Column, Integer, BigInteger, String
from database import database, session
class Item(database.base):
__tablename__ = "bistro_bistro_item"
idx = Column(Integer, primary_key=True)
guild_id = Column(BigInteger)
name = Column(String)
description = Column(String)
@staticmethod
def add(guild_id: int, name: str, description: str) -> Item:
query = Item(
guild_id=guild_id,
name=name,
description=description
)
session.add(query)
session.commit()
return query
@staticmethod
def get(guild_id: int, name: str) -> Optional[Item]:
query = session.query(Item).filter_by(
guild_id=guild_id,
name=name,
).one_or_none()
return query
@staticmethod
def remove(guild_id: int, name: str) -> int:
query = session.query(Item).filter_by(
guild_id=guild_id,
name=name,
).delete()
return query
def save(self):
session.commit()
def __repr__(self) -> str:
return (
f'<Item idx="{self.idx}" '
f'guild_id="{self.guild_id}" name="{self.name}" '
f'description="{self.description}">'
)
def dump(self) -> dict:
return {
"guild_id": self.guild_id,
"name": self.name,
"description": self.description,
}
Testing¶
You MAY include a directory called tests/ in the root of the repository (e.g. between the module directories). This directory will be ignored by lemon.py module checks and won’t emit “Invalid module” warnings.
Please note that this may be changed in the future and some lemon.py versions may require the modules to be subclassed in modules/ directory, if this proves to be confusing.