Skip to content

Commit

Permalink
Merge branch fix/argument-parsing into master (#32)
Browse files Browse the repository at this point in the history
* Refresh the contributing guide

* Move argument processing to its own function

This should make future development easier

* Allow loading the library if `magic` is missing or unavailable

* Formatting

* Support keyword args in Argument()

* Require that *, args are strings

* Skip ctx in command parsing

* Log which arguments are being parsed

* Fix kwargs having non-string keys

Also fix kwargs log message logging args

* Unskip ctx.args[0]

I'm not sure why this fixed what was broken but broke it in a different way too

* start at index 1 when actually parsing args

* don't prematurely await parsing

* Add documentation regarding different argument types

* Fix str sometimes erroneously being detected as invalid in arg parsing

Fixes "TypeError: Keyword-only arguments must be of type str.", caused by "WARNING - Argument got an instance of a type, not a type itself: 'str'. Inspect as if it was its rawtype, <class 'str'>"

* Fix the builtin str parsing not working, again

* Fix python 3.9 and 3.10 support
  • Loading branch information
nexy7574 authored Nov 21, 2024
1 parent 6a8bbb2 commit 0a4b8d5
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 82 deletions.
40 changes: 19 additions & 21 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Making NioBot better

Pull requests and issues are always welcome for niobot! Here's a few things to keep in mind when contributing:
Issues and pull requests are the lifeblood of nio-bot. Opening issues is one of the most helpful things you can do,
and pull requests are even better. This document will guide you through the process of contributing to nio-bot.
From hereon, the term "contributing" will refer to both opening issues and pull requests, picking which is applicable.

Here's a few things to keep in mind when contributing:

## Scope

Expand Down Expand Up @@ -39,34 +43,25 @@ that is too complicated, it may be difficult to maintain, and may be removed in

## Code style

~~NioBot uses a very loose code style, however generally, make sure your code is readable, and that the max line length
of your code is 120 characters at a max, preferably less than 119.~~

~~If you are unsure about the style of your code, you should run `black ./src` and `isort` in the root directory of the
project. In `pyproject.toml`, all the configuration for those tools is already set up, so you don't need to worry about
command line flags.~~

~~You should also ensure there are no warnings with `pycodestyle`.~~

NioBot now makes use of `ruff` for code formatting, and this is automatically enforced by the CI.
You should run `ruff format` in the root directory of the project to format your code before submitting a pull request.
The rules that are used for formatting are already pre-created in the pyproject.toml file, so you do not need to worry
about arguments.
If you just want to check that your code is following the code style without making any changes, run `ruff check`.

### Versions
Pre-commit is available if you desire, and there are CI checks to ensure that your code is formatted correctly.

NioBot uses SemVer (semantic versioning) for versioning. This means that the version number is split into three parts:
`Major`, `Minor` and `Patch`. As per the versioning, `Major` versions are not guaranteed to be backwards compatible,
however `Minor` and `Patch` versions are.
### Versions

This means that there will always be a new `Major` increment when a backwards incompatible change is made, and a new
`Minor` increment when a backwards compatible change is made. `Patch` versions are almost always bug fixes, and are
always backwards compatible. If a bug fix is not backwards compatible, a new `Major` version will be released.
NioBot very loosely uses [Semantic Versioning](https://semver.org/).
You've probably heard of semver before, but if you haven't there's 3 parts to a version number: `MAJOR.MINOR.PATCH`.
These are incremented with MAJOR (API incompatible) changes, MINOR (backwards-compatible) changes,
and PATCH (backwards-compatible bug fixes) changes.

Note that in the event a breaking however minor change is made, `Minor` will be the only one increased. For example,
if there's a simple parameter change (e.g. name or type or becomes required), `Minor` will be incremented, however
old signatures and methods will still exist for the rest of the current Major release, or for 5 future Minor versions.
When "loosely uses" is used, it means that nio-bot will try to follow semver as closely as possible, however,
the MAJOR segment does not get bumped with every breaking change, only if there are several.
For example, if a non-core function signature changes incompatibly, it will not bump the major version. However, if
several functions change incompatibly, it will bump the major version.

Major changes may be pushed into their own branches for "feature previews". These branches will be prefixed with
`feature/`, and will be merged into `master` when they are ready for release. For example, `feature/my-thing`,
Expand All @@ -91,12 +86,15 @@ release candidates.
Furthermore, in the interest of backward compatibility, it may take a while until nio-bot supports the latest
language features. Keep this in mind.

**End of life versions are never actively supported**. See [EOL.date](https://endoflife.date/python) for more
information.

# Community

The great thing about open source software is the ability for anyone to read, understand, and contribute to it.
NioBot, with our strong copy-left [LGPLv3](/LICENSE) license, is no exception.

If you think there's something that could benefit nio-bot users, however don't think its in scope or relevant to the
If you think there's something that could benefit nio-bot users, however don't think it's in scope or relevant to the
core library, you are welcome to create a community plugin. Community plugins are plugins that are not part of the
core library, however can still be installed and used by nio-bot users.

Expand Down
61 changes: 61 additions & 0 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,65 @@

Using commands and events is the main way to interact with the bot.

## Command argument detection

One of the most powerful features of NioBot is the command argument interpretation system.
When you create a niobot command, the arguments are automatically detected, and their desired
type is inferred from the type hints in the function signature.

This means that `foo: str` will always give you a string, `bar: int` will try to give you an integer,
or throw an error if it cannot convert the user-given argument.

As of v1.2.0, you can take advantage of the keyword-only and positional args in Python.
Normally, when you specify a function like `async def mycommand(ctx, x: str)`, niobot will see
that you want an argument, x, and will do just that. It will take the user's input, and give you
the value for x. However, if the user specifies multiple words for `x`, it will only give the first one
to the function, unless the user warps the argument in "quotes".

```python
import niobot
bot = niobot.NioBot()

@bot.command()
async def mycommand(ctx, x: str):
await ctx.respond(f"Your argument was: {x}")
```
If you ran `!mycommand hello world`, the bot would respond with `Your argument was: hello`.

With keyword-only arguments, you can make use of "greedy" arguments.
While you could previously do this by *manually* constructing the [niobot.Argument][] type,
you can now do this with the `*` syntax in Python.

```python
import niobot
bot = niobot.NioBot()

@bot.command()
async def mycommand(ctx, *, x: str):
await ctx.respond(f"Your argument was: {x}")
```
If you ran `!mycommand hello world`, the bot would respond with `Your argument was: hello world`.

And, as for positional args, if you want to fetch a set of arguments, you can do so by specifying
`*args`. This will give you a tuple containing every whitespace-delimited argument after the command.

```python
import niobot
bot = niobot.NioBot()

@bot.command()
async def mycommand(ctx, *args: str):
await ctx.respond(f"Your arguments were: {args}")
```
If you ran `!mycommand hello world`, the bot would respond with `Your arguments were: ('hello', 'world')`.

!!! danger "Position & KW-Only args are final and strings!"
If you specify a keyword or positional argument, you cannot have any arguments afterwards.
Furthermore, (currently) both of these arguments are always strings. Trying to specify
another type will throw an error.

---

## Reference

::: niobot.commands
13 changes: 12 additions & 1 deletion src/niobot/attachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@
import aiofiles
import aiohttp
import blurhash
import magic

try:
import magic
except ImportError:
logging.getLogger(__name__).critical(
"Failed to load magic. Automatic file type detection will be unavailable. Please install python3-magic."
)
magic = None
import nio

from .exceptions import (
Expand Down Expand Up @@ -95,7 +102,11 @@ def detect_mime_type(file: U[str, io.BytesIO, pathlib.Path]) -> str:
:param file: The file to detect the mime type of. Can be a BytesIO.
:return: The mime type of the file (e.g. `text/plain`, `image/png`, `application/pdf`, `video/webp` etc.)
:raises RuntimeError: If the `magic` library is not installed.
:raises TypeError: If the file is not a string, BytesIO, or Path object.
"""
if not magic:
raise RuntimeError("magic is not installed. Please install it to use this function.")
if isinstance(file, str):
file = pathlib.Path(file)

Expand Down
3 changes: 2 additions & 1 deletion src/niobot/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ def __init__(
elif asyncio.iscoroutinefunction(cmd) or inspect.isfunction(cmd):
self.log.warning(
"Manually changing default help command callback to %r. Please consider passing your own"
" Command instance instead."
" Command instance instead.",
cmd,
)
help_cmd.callback = cmd
else:
Expand Down
Loading

0 comments on commit 0a4b8d5

Please sign in to comment.