Updated Development_Contribution (markdown)

master
Chris Caron 2021-08-02 17:43:17 -04:00
parent e7feca027c
commit 60e91c57dc
1 changed files with 62 additions and 45 deletions

@ -1,3 +1,4 @@
# Introduction
Thanks to all who have landed on this page with the intent of contributing to the apprise library. Any changes you make are going to easily make it upstream as long as there is there are:
* **Unit tests**: apprise is currently sitting at 100% test coverage. The goal is to keep it this way! :slightly_smiling_face:
* **PEP8 Compliance**: Following the [PEP 8 Style Guide for Python](https://www.python.org/dev/peps/pep-0008/) is a must. Most editors have PEP8 plugins and allow you to keep everything compliant as you go.
@ -9,55 +10,71 @@ The following should get you all set up:
pip install --requirement requirements.txt --requirement dev-requirements.txt
```
I also have this small (very simple) script that I run each time before I push my changes. It may or may not be useful to others:
```bash
#!/bin/sh
# Description: This is a checkdone.sh script used to check against the
# apprise project that I reference before I commit any code
# upstream.
#
# Note: this script was intended to be ran from within the
# apprise root directory (after being checked out from
# github).
cleanup() {
# Simple safety checking (ensure we're in the right dir)
[ ! -d "apprise" ] && return 1
[ ! -d "apprise/plugins" ] && return 1
[ ! -d "apprise/config" ] && return 1
[ ! -d "test" ] && return 1
# Testing
There is a few tools that work right out of the box in the root of any branch you're working in. These tools allow you to clone the Apprise branch and immediately test your changes without having to install anything into your environment.
# we can safely assume we're being ran at the root of the apprise
# directory
rm -rf .tox/ .eggs .coverage .cache \
apprise.egg-info htmlcov build &>/dev/null
find . -type d -name __pycache__ \
-exec rm -rf {} \; &>/dev/null
find . -maxdepth 1 -mindepth 1 -type f \
-name '.coverage.*' -delete
return 0
}
More details can be found [here](https://github.com/caronc/apprise/tree/master/bin) about them.
# Incase it wasn't ran before (or we ctrl-c'ed last time)
cleanup
# Building Your Own Notification Plugin
flake8 . --show-source --statistics && \
flake8-3 . --show-source --statistics
if [ $? -ne 0 ]; then
echo "pep8 failed; early exit."
exit 1
fi
It basically boils down to this:
- You just need to create you just need to create a single notification in the `/plugins/NotifyServiceName.py`
- Make sure you call the class inside `NotifyServiceName` and inherit from `NotifyBase`
- From there you just need to at a bare minimum define:
- **the class objects**:
- `service_name`: A string that acts as a default descriptive name associated with the Notification
- `service_url`: A string that identifies the platform/services URL. This is used purely as meta data for those who seek it. But this field is required.
- `secure_protocol`: A string (or can be a list of strings) identifying the scheme:// keyword. So setting this to say `'bad'` means that this class you're creating will be instantiated if someone tries to access `bad://`
- `setup_url`: A string that identifies the URL a user can use to get information on how to use this Apprise Notification. At this time I'm just creating URLs that point back to my GitHub Wiki page.
- `templates`, `template_tokens`, and `template_args`. It's best to look at all of the many services that exist to see how to prepare this. But this is just a way of identifying what the class accepts as arguments. You can optionally identify some regular expressions to validate the arguments as well. This is provided as both meta data to those requesting it (it plays a huge role in the API i built as the JSON output is based on this). This data is also heavily validated using unit tests. So if you identify something there that isn't defined in the `__init__()` of your class, or vs versa (you declare something in your `__init__()` and do not identify it here. Then you'll get an error (tests will not pass). The problem is the error is rather cryptic; (this is what is happening to you; you've identified `secret` in only one location)
- **the functions**:
- `__init__(self, *args, **kwargs)`: Define what is required to initialize your object/notification. Just make sure to cross reference it in the `template*` stuff (explained above).
-`send(self, body, title='', *args, **kwargs)` at a bare minimum. See other Notify scripts as to how you can expand on this. But just take the `body` and `title` and construct your message and send it.
- `url()`. This function must be able to construct a URL that would re-generate a copy of the exact same object if passed into `parse_url()`
- `parse_url(url)`: this is a **staticmethod** that parses the Apprise URL and breaks it into a dictionary of the components. The dictionary it creates must map up to what the `__init__()` takes as it's arguments
- **Putting it together**:
```python
from Apprise.plugins import NotifyMyService
import Apprise
# Apprise is nothing but a manager of individual plugins
a = Apprise()
a.add('myscheme://details/?more=details&are=optional')
# There would be one new service added to our manager now:
assert(len(a), 1)
coverage run --parallel -m pytest -vv && \
coverage3 run --parallel -m pytest -vv
if [ $? -ne 0 ]; then
echo "tests failed; early exit."
exit 1
fi
# you can directly access the notification services if you wanted to this way:
# index element 0 exists because we added it successfully above (assuming you properly
# followed all the rules above):
assert isinstance(a[0], NotifyMyService)
# So we know we can access the notification, then this would create a second notification service:
# The only thing add does is match the schema up with the class it should use and then call it's
# NotifyServiceName.parse_url()
# So parse_url() is in charge of preparing all of the arguments we can use to instantiate our object
# With that, it can then do Object(**parse_url_response)
a.add(a[0].url())
coverage combine
coverage report --show-missing
# Hopefully this is making sense so far.... But now we've called add() twice... so we'll ahve 2 entries
# and if we built our 3 core functions (__init__, `url()` and `parse_url()` correctly, they should be almost
# copies of one another (yet 2 instances)
assert(len(a) == 2)
# URLs are the same
assert(a[0].url() == a[1].url())
# that's really all there is too it... when you call `a.notify()`; there is some functions and tools
# that handle some common things, but at the end of the day, it will call your `send()` function
# you defined.
```
Any other functions you want to define can be done to you hearts content (if it helps with organization, structure, etc)
Just avoid conflicting with any function written in `NotifyBase()` and `URLBase()`
# Cleanup
cleanup
```
If your service is really complex (and requires a lot of code), maybe it's easier to split your code into multiple files. This is how i handled the [NotifyFCM plugin i wrote](https://github.com/caronc/apprise/tree/master/apprise/plugins/NotifyFCM) which was based on Google's version.
You can have a look at the NotifyBase object and see all of the other entries you can define that Apprise can look after for you (such as restricting the message length, title length, handling TEXT -> Markdown, etc). You can also look at how other classes were built.