Compare commits
33 Commits
Author | SHA1 | Date |
---|---|---|
|
584186e831 | |
|
9763ca5fbf | |
|
904aa06629 | |
|
be715d201c | |
|
647925acd3 | |
|
e5100d499e | |
|
9ac1d55d02 | |
|
bf1c18f347 | |
|
e381c1b522 | |
|
77a881980b | |
|
5710d46874 | |
|
9794b00cc0 | |
|
5ebaa04f3d | |
|
0f19cf4a9e | |
|
8d3026523a | |
|
1e43a4e12d | |
|
45e1f551d8 | |
|
056e0217aa | |
|
acce3a83fe | |
|
775fb89cf6 | |
|
bf3170cb6f | |
|
ff7567dc1b | |
|
f16cffa44e | |
|
7bcb374891 | |
|
b17dc556d7 | |
|
201e259d37 | |
|
9744f436ae | |
|
34ebd8f982 | |
|
aaff82fe98 | |
|
1c7e3c7ed5 | |
|
c40a7aca35 | |
|
4757cc6ec8 | |
|
6bd2d606df |
2
.envrc
2
.envrc
|
@ -13,7 +13,7 @@ if has guix; then
|
||||||
pre-commit uninstall
|
pre-commit uninstall
|
||||||
fi
|
fi
|
||||||
if [ ! -d "$venv_dir" ] ; then
|
if [ ! -d "$venv_dir" ] ; then
|
||||||
virtualenv -p `which python3.9` "$venv_dir"
|
virtualenv -p `which python3` "$venv_dir"
|
||||||
poetry install
|
poetry install
|
||||||
pre-commit install
|
pre-commit install
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -5,10 +5,34 @@ name: CI
|
||||||
# Controls when the workflow will run
|
# Controls when the workflow will run
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- 'guix.scm'
|
||||||
|
- 'manifest.scm'
|
||||||
|
- 'channels-lock.scm'
|
||||||
|
- '.envrc'
|
||||||
|
- '.gitignore'
|
||||||
|
- 'pre-commit-*.yaml'
|
||||||
|
- Dockerfile
|
||||||
|
- README.*
|
||||||
|
- LICENSE
|
||||||
|
- 'sample_settings/**'
|
||||||
|
- 'etc/**'
|
||||||
|
|
||||||
push:
|
push:
|
||||||
# Sequence of patterns matched against refs/tags
|
# Sequence of patterns matched against refs/tags
|
||||||
branches: ["master"]
|
branches: ["master"]
|
||||||
|
paths-ignore:
|
||||||
|
- 'guix.scm'
|
||||||
|
- 'manifest.scm'
|
||||||
|
- 'channels-lock.scm'
|
||||||
|
- '.envrc'
|
||||||
|
- '.gitignore'
|
||||||
|
- 'pre-commit-*.yaml'
|
||||||
|
- Dockerfile
|
||||||
|
- README.*
|
||||||
|
- LICENSE
|
||||||
|
- 'sample_settings/**'
|
||||||
|
- 'etc/**'
|
||||||
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -16,20 +40,32 @@ on:
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
jobs:
|
jobs:
|
||||||
run-tests-dev:
|
run-tests-dev:
|
||||||
# The type of runner that the job will run on
|
strategy:
|
||||||
runs-on: ubuntu-latest
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
python-version: ["3.10", "3.11"]
|
||||||
|
poetry-version: ["1.1.12", "1.7.0"]
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/setup-python@v4
|
||||||
|
|
||||||
# Runs a single command using the runners shell
|
|
||||||
- name: Set up Python 3.10
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Run image
|
||||||
|
uses: abatilo/actions-poetry@v2
|
||||||
|
with:
|
||||||
|
poetry-version: ${{ matrix.poetry-version }}
|
||||||
|
- name: Setup a local virtual environment
|
||||||
|
run: |
|
||||||
|
poetry config virtualenvs.create true --local
|
||||||
|
poetry config virtualenvs.in-project true --local
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
name: Define a cache for the virtual environment based on the dependencies lock file
|
||||||
|
with:
|
||||||
|
path: ./.venv
|
||||||
|
key: venv-${{ hashFiles('poetry.lock') }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: scripts/install_github_actions_dev_dependencies.sh
|
run: scripts/install_github_actions_dev_dependencies.sh
|
||||||
- name: Run tests in dev env
|
- name: Run tests in dev env
|
||||||
run: scripts/run_pipeline_tests.sh
|
run: scripts/run_pipeline_tests.sh
|
||||||
|
|
|
@ -27,11 +27,11 @@ jobs:
|
||||||
|
|
||||||
# Runs a single command using the runners shell
|
# Runs a single command using the runners shell
|
||||||
- name: Install GNU Guix
|
- name: Install GNU Guix
|
||||||
uses: PromyLOPh/guix-install-action@v1
|
uses: PromyLOPh/guix-install-action@v1.4
|
||||||
|
|
||||||
# Runs a set of commands using the runners shell
|
# Runs a set of commands using the runners shell
|
||||||
- name: Build image
|
- name: Build image
|
||||||
run: scripts/build_docker_image.sh
|
run: scripts/build_docker_image.sh -r
|
||||||
- name: Upload pack (Docker)
|
- name: Upload pack (Docker)
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
|
@ -59,9 +59,9 @@ jobs:
|
||||||
uses: fishinthecalculator/publish-docker-image-action@v0.1.10
|
uses: fishinthecalculator/publish-docker-image-action@v0.1.10
|
||||||
env:
|
env:
|
||||||
IMAGE_TAG: ${{ steps.vars.outputs.tag }}
|
IMAGE_TAG: ${{ steps.vars.outputs.tag }}
|
||||||
IMAGE_NAME_TAG: mobilizon-reshare-scheduler:latest
|
IMAGE_NAME_TAG: mobilizon-reshare-scheduler-python:latest
|
||||||
with:
|
with:
|
||||||
name: fishinthecalculator/mobilizon-reshare
|
name: twcita/mobilizon-reshare
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
image: docker-image.tar.gz
|
image: docker-image.tar.gz
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="118.9" height="20"><linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="round"><rect width="118.9" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#round)"><rect width="56.2" height="20" fill="#555"/><rect x="56.2" width="62.7" height="20" fill="#007ec6"/><rect width="118.9" height="20" fill="url(#smooth)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="291.0" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="462.0" lengthAdjust="spacing">LICENSE</text><text x="291.0" y="140" transform="scale(0.1)" textLength="462.0" lengthAdjust="spacing">LICENSE</text><text x="865.5000000000001" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="527.0" lengthAdjust="spacing">Coopyleft</text><text x="865.5000000000001" y="140" transform="scale(0.1)" textLength="527.0" lengthAdjust="spacing">Coopyleft</text><a xlink:href="https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare/blob/master/LICENSE"><rect width="56.2" height="20" fill="rgba(0,0,0,0)"/></a><a xlink:href="https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare/blob/master/LICENSE"><rect x="56.2" width="62.7" height="20" fill="rgba(0,0,0,0)"/></a></g></svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="71.6" height="20"><linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="round"><rect width="71.6" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#round)"><rect width="33.6" height="20" fill="#555"/><rect x="33.6" width="38.0" height="20" fill="#007ec6"/><rect width="71.6" height="20" fill="url(#smooth)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="178.0" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="236.0" lengthAdjust="spacing">pypi</text><text x="178.0" y="140" transform="scale(0.1)" textLength="236.0" lengthAdjust="spacing">pypi</text><text x="516.0" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="280.0" lengthAdjust="spacing">0.3.6</text><text x="516.0" y="140" transform="scale(0.1)" textLength="280.0" lengthAdjust="spacing">0.3.6</text><a xlink:href="https://pypi.org/project/mobilizon-reshare/"><rect width="33.6" height="20" fill="rgba(0,0,0,0)"/></a><a xlink:href="https://pypi.org/project/mobilizon-reshare/"><rect x="33.6" width="38.0" height="20" fill="rgba(0,0,0,0)"/></a></g></svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="131.5" height="20"><linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="round"><rect width="131.5" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#round)"><rect width="65.5" height="20" fill="#555"/><rect x="65.5" width="66.0" height="20" fill="#007ec6"/><rect width="131.5" height="20" fill="url(#smooth)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj4KICA8ZGVmcz4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0icHlZZWxsb3ciIGdyYWRpZW50VHJhbnNmb3JtPSJyb3RhdGUoNDUpIj4KICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iI2ZlNSIgb2Zmc2V0PSIwLjYiLz4KICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iI2RhMSIgb2Zmc2V0PSIxIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJweUJsdWUiIGdyYWRpZW50VHJhbnNmb3JtPSJyb3RhdGUoNDUpIj4KICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iIzY5ZiIgb2Zmc2V0PSIwLjQiLz4KICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iIzQ2OCIgb2Zmc2V0PSIxIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KCiAgPHBhdGggZD0iTTI3LDE2YzAtNyw5LTEzLDI0LTEzYzE1LDAsMjMsNiwyMywxM2wwLDIyYzAsNy01LDEyLTExLDEybC0yNCwwYy04LDAtMTQsNi0xNCwxNWwwLDEwbC05LDBjLTgsMC0xMy05LTEzLTI0YzAtMTQsNS0yMywxMy0yM2wzNSwwbDAtM2wtMjQsMGwwLTlsMCwweiBNODgsNTB2MSIgZmlsbD0idXJsKCNweUJsdWUpIi8+CiAgPHBhdGggZD0iTTc0LDg3YzAsNy04LDEzLTIzLDEzYy0xNSwwLTI0LTYtMjQtMTNsMC0yMmMwLTcsNi0xMiwxMi0xMmwyNCwwYzgsMCwxNC03LDE0LTE1bDAtMTBsOSwwYzcsMCwxMyw5LDEzLDIzYzAsMTUtNiwyNC0xMywyNGwtMzUsMGwwLDNsMjMsMGwwLDlsMCwweiBNMTQwLDUwdjEiIGZpbGw9InVybCgjcHlZZWxsb3cpIi8+CgogIDxjaXJjbGUgcj0iNCIgY3g9IjY0IiBjeT0iODgiIGZpbGw9IiNGRkYiLz4KICA8Y2lyY2xlIHI9IjQiIGN4PSIzNyIgY3k9IjE1IiBmaWxsPSIjRkZGIi8+Cjwvc3ZnPgo="/><text x="422.5" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="385.0" lengthAdjust="spacing">python</text><text x="422.5" y="140" transform="scale(0.1)" textLength="385.0" lengthAdjust="spacing">python</text><text x="975.0" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="560.0" lengthAdjust="spacing">3.10, 3.11</text><text x="975.0" y="140" transform="scale(0.1)" textLength="560.0" lengthAdjust="spacing">3.10, 3.11</text><a xlink:href="https://www.python.org/"><rect width="65.5" height="20" fill="rgba(0,0,0,0)"/></a><a xlink:href="https://www.python.org/"><rect x="65.5" width="66.0" height="20" fill="rgba(0,0,0,0)"/></a></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -3,7 +3,7 @@ repos:
|
||||||
rev: stable
|
rev: stable
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language_version: python3.9
|
language_version: python3.10
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v1.2.3
|
rev: v1.2.3
|
||||||
hooks:
|
hooks:
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
[![CI](https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare/actions/workflows/main.yml)
|
[![CI](https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare/actions/workflows/main.yml)
|
||||||
|
[![Python versions](https://raw.githubusercontent.com/Tech-Workers-Coalition-Italia/mobilizon-reshare/master/.img/python.svg)](https://python.org)
|
||||||
|
[![PyPI version](https://raw.githubusercontent.com/Tech-Workers-Coalition-Italia/mobilizon-reshare/master/.img/pypi.svg)](https://pypi.org/project/mobilizon-reshare/)
|
||||||
|
[![License](https://raw.githubusercontent.com/Tech-Workers-Coalition-Italia/mobilizon-reshare/master/.img/license.svg)](https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare/blob/master/LICENSE)
|
||||||
|
|
||||||
The goal of `mobilizon_reshare` is to provide a suite to reshare Mobilizon events on a broad selection of platforms. This
|
The goal of `mobilizon_reshare` is to provide a suite to reshare Mobilizon events on a broad selection of platforms. This
|
||||||
tool enables an organization to automate their social media strategy in regards
|
tool enables an organization to automate their social media strategy in regards
|
||||||
|
@ -37,7 +40,7 @@ commands and their description.
|
||||||
|
|
||||||
### Guix package
|
### Guix package
|
||||||
|
|
||||||
If you run Guix you can install `mobilizon-reshare` by adding our [Guix channel](https://github.com/fishinthecalculator/mobilizon-reshare-guix#configure) to your `.config/guix/channels.scm`.
|
If you run Guix you can install `mobilizon-reshare` by adding our [Guix channel](https://git.sr.ht/~fishinthecalculator/mobilizon-reshare-guix#configure) to your `.config/guix/channels.scm`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
(list
|
(list
|
||||||
(channel
|
(channel
|
||||||
(name 'mobilizon-reshare)
|
(name 'mobilizon-reshare)
|
||||||
(url "https://github.com/fishinthecalculator/mobilizon-reshare-guix")
|
(url "https://git.sr.ht/~fishinthecalculator/mobilizon-reshare-guix")
|
||||||
(branch "main"))
|
(branch "main"))
|
||||||
(channel
|
(channel
|
||||||
(name 'guix)
|
(name 'guix)
|
||||||
(url "https://git.savannah.gnu.org/git/guix.git")
|
(url "https://git.savannah.gnu.org/git/guix.git")
|
||||||
(commit
|
(commit
|
||||||
"79a3cd34c0318928186a04b6481c4d22c0051d04")
|
"b7eb1a8116b2caee7acf26fb963ae998fbdb4253")
|
||||||
(introduction
|
(introduction
|
||||||
(make-channel-introduction
|
(make-channel-introduction
|
||||||
"afb9f2752315f131e4ddd44eba02eed403365085"
|
"afb9f2752315f131e4ddd44eba02eed403365085"
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
version: "3.7"
|
version: "3.7"
|
||||||
services:
|
services:
|
||||||
mobilizon-reshare:
|
mobilizon-reshare:
|
||||||
image: twcita/mobilizon-reshare:v0.3.2
|
image: twcita/mobilizon-reshare:v0.3.6
|
||||||
environment:
|
environment:
|
||||||
SECRETS_FOR_DYNACONF: /etc/xdg/mobilizon-reshare/0.3.2/.secrets.toml
|
SECRETS_FOR_DYNACONF: /etc/xdg/mobilizon-reshare/0.3.6/.secrets.toml
|
||||||
ENV_FOR_DYNACONF: production
|
ENV_FOR_DYNACONF: production
|
||||||
MOBILIZON_RESHARE_INTERVAL: "*/15 10-18 * * 0-4"
|
MOBILIZON_RESHARE_INTERVAL: "*/15 10-18 * * 0-4"
|
||||||
volumes:
|
volumes:
|
||||||
- ./.secrets.toml:/etc/xdg/mobilizon-reshare/0.3.2/.secrets.toml:ro
|
- ./.secrets.toml:/etc/xdg/mobilizon-reshare/0.3.6/.secrets.toml:ro
|
||||||
- ./mobilizon_reshare.toml:/etc/xdg/mobilizon-reshare/0.3.2/mobilizon_reshare.toml:ro
|
- ./mobilizon_reshare.toml:/etc/xdg/mobilizon-reshare/0.3.6/mobilizon_reshare.toml:ro
|
||||||
- ./var:/var/lib/mobilizon-reshare
|
- ./var:/var/lib/mobilizon-reshare
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- /etc/timezone:/etc/timezone:ro
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
|
91
guix.scm
91
guix.scm
|
@ -4,11 +4,9 @@
|
||||||
#:use-module (guix gexp)
|
#:use-module (guix gexp)
|
||||||
#:use-module (guix packages)
|
#:use-module (guix packages)
|
||||||
#:use-module (guix utils)
|
#:use-module (guix utils)
|
||||||
#:use-module (gnu packages databases) ;; for python-tortoise-orm
|
#:use-module (gnu packages markup) ;; for python-markdownify
|
||||||
#:use-module (gnu packages markup) ;; for python-markdownify
|
#:use-module (gnu packages python-web) ;; for python-fastapi-pagination-minimal and uvicorn
|
||||||
#:use-module (gnu packages python)
|
#:use-module (gnu packages python-xyz) ;; for python-apscheduler
|
||||||
#:use-module (gnu packages python-web) ;; for python-uvicorn
|
|
||||||
#:use-module (gnu packages python-xyz) ;; for dynaconf
|
|
||||||
#:use-module (mobilizon-reshare package)
|
#:use-module (mobilizon-reshare package)
|
||||||
#:use-module (mobilizon-reshare dependencies)
|
#:use-module (mobilizon-reshare dependencies)
|
||||||
#:use-module (ice-9 rdelim)
|
#:use-module (ice-9 rdelim)
|
||||||
|
@ -21,33 +19,7 @@
|
||||||
#:recursive? #t
|
#:recursive? #t
|
||||||
#:select? (git-predicate %source-dir)))
|
#:select? (git-predicate %source-dir)))
|
||||||
|
|
||||||
(use-modules (guix download)
|
(define mobilizon-reshare.git
|
||||||
(guix transformations))
|
|
||||||
(define-public python-tweepy-4.13
|
|
||||||
(package
|
|
||||||
(inherit python-tweepy)
|
|
||||||
(version "4.13.0")
|
|
||||||
(source (origin
|
|
||||||
(method url-fetch)
|
|
||||||
(uri (pypi-uri "tweepy" version))
|
|
||||||
(sha256
|
|
||||||
(base32
|
|
||||||
"123cikpmp2m360pxh2qarb4kkjmv8wi2prx7df178rlzbwrjax09"))))
|
|
||||||
(arguments
|
|
||||||
`(#:tests? #f))))
|
|
||||||
|
|
||||||
(define-public python-oauthlib-3.2
|
|
||||||
(package
|
|
||||||
(inherit python-oauthlib)
|
|
||||||
(version "3.2.2")
|
|
||||||
(source (origin
|
|
||||||
(method url-fetch)
|
|
||||||
(uri (pypi-uri "oauthlib" version))
|
|
||||||
(sha256
|
|
||||||
(base32
|
|
||||||
"066r7mimlpb5q1fr2f1z59l4jc89kv4h2kgkcifyqav6544w8ncq"))))))
|
|
||||||
|
|
||||||
(define _mobilizon-reshare.git
|
|
||||||
(let ((source-version (with-input-from-file
|
(let ((source-version (with-input-from-file
|
||||||
(string-append %source-dir
|
(string-append %source-dir
|
||||||
"/mobilizon_reshare/VERSION")
|
"/mobilizon_reshare/VERSION")
|
||||||
|
@ -55,43 +27,24 @@
|
||||||
(revision "0")
|
(revision "0")
|
||||||
(commit (read-line
|
(commit (read-line
|
||||||
(open-input-pipe "git show HEAD | head -1 | cut -d ' ' -f 2"))))
|
(open-input-pipe "git show HEAD | head -1 | cut -d ' ' -f 2"))))
|
||||||
(package (inherit mobilizon-reshare)
|
((package-input-rewriting/spec `(("python-fastapi" . ,(const python-fastapi))
|
||||||
(name "mobilizon-reshare.git")
|
("python-dotenv" . ,(const python-dotenv-0.13.0))
|
||||||
(version (git-version source-version revision commit))
|
("python-uvicorn" . ,(const python-uvicorn))))
|
||||||
(source mobilizon-reshare-git-origin)
|
(package (inherit mobilizon-reshare)
|
||||||
(arguments
|
(name "mobilizon-reshare.git")
|
||||||
(substitute-keyword-arguments (package-arguments mobilizon-reshare)
|
(version (git-version source-version revision commit))
|
||||||
((#:phases phases)
|
(source mobilizon-reshare-git-origin)
|
||||||
#~(modify-phases #$phases
|
(propagated-inputs
|
||||||
(add-after 'unpack 'patch-version
|
(modify-inputs (package-propagated-inputs mobilizon-reshare)
|
||||||
(lambda _
|
(replace "python-uvicorn" python-uvicorn)
|
||||||
(with-output-to-file "mobilizon_reshare/VERSION"
|
(replace "python-fastapi" python-fastapi)
|
||||||
(lambda _
|
(replace "python-fastapi-pagination-minimal"
|
||||||
(display #$version)))))
|
(package
|
||||||
(delete 'patch-pyproject.toml)))))
|
(inherit python-fastapi-pagination-minimal)
|
||||||
(native-inputs
|
(propagated-inputs
|
||||||
(modify-inputs (package-native-inputs mobilizon-reshare)
|
(modify-inputs (package-propagated-inputs python-fastapi-pagination-minimal)
|
||||||
(prepend python-httpx)))
|
(replace "python-fastapi" python-fastapi)))))
|
||||||
(propagated-inputs
|
(replace "python-markdownify" python-markdownify)))))))
|
||||||
(modify-inputs (package-propagated-inputs mobilizon-reshare)
|
|
||||||
(prepend python-asyncpg
|
|
||||||
python-uvicorn
|
|
||||||
python-fastapi
|
|
||||||
python-fastapi-pagination)
|
|
||||||
(replace "python-tweepy"
|
|
||||||
python-tweepy-4.13)
|
|
||||||
(replace "dynaconf"
|
|
||||||
dynaconf-3.1.11)
|
|
||||||
(replace "python-markdownify"
|
|
||||||
python-markdownify))))))
|
|
||||||
|
|
||||||
(define-public patch-for-mobilizon-reshare-0.3.3
|
|
||||||
(package-input-rewriting/spec `(("python-oauthlib" . ,(const python-oauthlib-3.2))
|
|
||||||
("python-beautifulsoup4" . ,(const python-beautifulsoup4))
|
|
||||||
("python-tortoise-orm" . ,(const python-tortoise-orm)))))
|
|
||||||
|
|
||||||
(define-public mobilizon-reshare.git
|
|
||||||
(patch-for-mobilizon-reshare-0.3.3 _mobilizon-reshare.git))
|
|
||||||
|
|
||||||
(define-public mobilizon-reshare-scheduler
|
(define-public mobilizon-reshare-scheduler
|
||||||
(package (inherit mobilizon-reshare.git)
|
(package (inherit mobilizon-reshare.git)
|
||||||
|
|
|
@ -12,6 +12,6 @@
|
||||||
(map cadr (package-direct-inputs mobilizon-reshare))
|
(map cadr (package-direct-inputs mobilizon-reshare))
|
||||||
(map specification->package+output
|
(map specification->package+output
|
||||||
'("git-cal" "man-db" "texinfo"
|
'("git-cal" "man-db" "texinfo"
|
||||||
"python-pre-commit" "cloc"
|
"pre-commit" "cloc"
|
||||||
"ripgrep" "python-semver"
|
"ripgrep" "python-semver"
|
||||||
"fd" "docker-compose" "poetry"))))
|
"fd" "docker-compose" "poetry"))))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[default.publisher.telegram]
|
[default.publisher.telegram]
|
||||||
active=true
|
active=true
|
||||||
chat_id="xxx"
|
chat_id="xxx"
|
||||||
|
message_thread_id="xxx"
|
||||||
token="xxx"
|
token="xxx"
|
||||||
username="xxx"
|
username="xxx"
|
||||||
[default.publisher.zulip]
|
[default.publisher.zulip]
|
||||||
|
@ -31,6 +32,7 @@ page_access_token="xxx"
|
||||||
[default.notifier.telegram]
|
[default.notifier.telegram]
|
||||||
active=true
|
active=true
|
||||||
chat_id="xxx"
|
chat_id="xxx"
|
||||||
|
message_thread_id="xxx"
|
||||||
token="xxx"
|
token="xxx"
|
||||||
username="xxx"
|
username="xxx"
|
||||||
[default.notifier.zulip]
|
[default.notifier.zulip]
|
||||||
|
@ -51,4 +53,4 @@ active=false
|
||||||
|
|
||||||
[default.notifier.facebook]
|
[default.notifier.facebook]
|
||||||
active=false
|
active=false
|
||||||
page_access_token="xxx"
|
page_access_token="xxx"
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.3.2
|
0.3.6
|
|
@ -5,6 +5,7 @@ import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from mobilizon_reshare.config.command import CommandConfig
|
from mobilizon_reshare.config.command import CommandConfig
|
||||||
|
from mobilizon_reshare.config.config import init_logging
|
||||||
from mobilizon_reshare.storage.db import tear_down, init
|
from mobilizon_reshare.storage.db import tear_down, init
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -15,6 +16,7 @@ async def graceful_exit():
|
||||||
|
|
||||||
|
|
||||||
async def _safe_execution(function):
|
async def _safe_execution(function):
|
||||||
|
init_logging()
|
||||||
await init()
|
await init()
|
||||||
|
|
||||||
return_code = 1
|
return_code = 1
|
||||||
|
|
|
@ -17,7 +17,7 @@ from mobilizon_reshare.cli.commands.retry.main import (
|
||||||
)
|
)
|
||||||
from mobilizon_reshare.cli.commands.start.main import start_command as start_main
|
from mobilizon_reshare.cli.commands.start.main import start_command as start_main
|
||||||
from mobilizon_reshare.config.command import CommandConfig
|
from mobilizon_reshare.config.command import CommandConfig
|
||||||
from mobilizon_reshare.config.config import current_version, get_settings
|
from mobilizon_reshare.config.config import current_version, get_settings, init_logging
|
||||||
from mobilizon_reshare.config.publishers import publisher_names
|
from mobilizon_reshare.config.publishers import publisher_names
|
||||||
from mobilizon_reshare.dataclasses.event import _EventPublicationStatus
|
from mobilizon_reshare.dataclasses.event import _EventPublicationStatus
|
||||||
from mobilizon_reshare.models.publication import PublicationStatus
|
from mobilizon_reshare.models.publication import PublicationStatus
|
||||||
|
@ -27,7 +27,8 @@ from mobilizon_reshare.publishers import get_active_publishers
|
||||||
def test_settings(ctx, param, value):
|
def test_settings(ctx, param, value):
|
||||||
if not value or ctx.resilient_parsing:
|
if not value or ctx.resilient_parsing:
|
||||||
return
|
return
|
||||||
get_settings()
|
settings = get_settings()
|
||||||
|
init_logging(settings)
|
||||||
click.echo("OK!")
|
click.echo("OK!")
|
||||||
ctx.exit()
|
ctx.exit()
|
||||||
|
|
||||||
|
@ -87,26 +88,21 @@ publication_status_argument = click.argument(
|
||||||
default="all",
|
default="all",
|
||||||
expose_value=True,
|
expose_value=True,
|
||||||
)
|
)
|
||||||
event_uuid_option = click.option(
|
force_publish_option = click.option(
|
||||||
"-E",
|
"-F",
|
||||||
"--event",
|
"--force",
|
||||||
type=click.UUID,
|
type=click.UUID,
|
||||||
expose_value=True,
|
expose_value=True,
|
||||||
help="Publish the given event.",
|
help="Publish the given event, bypassing all selection logic. This command WILL publish"
|
||||||
)
|
"regardless of the configured strategy, so use it with care.",
|
||||||
publication_uuid_option = click.option(
|
|
||||||
"-P",
|
|
||||||
"--publication",
|
|
||||||
type=click.UUID,
|
|
||||||
expose_value=True,
|
|
||||||
help="Publish the given publication.",
|
|
||||||
)
|
)
|
||||||
platform_name_option = click.option(
|
platform_name_option = click.option(
|
||||||
"-p",
|
"-p",
|
||||||
"--platform",
|
"--platform",
|
||||||
type=str,
|
type=str,
|
||||||
expose_value=True,
|
expose_value=True,
|
||||||
help="Publish to the given platform. This makes sense only for events.",
|
help="Restrict the platforms where the event will be published. This makes sense only in"
|
||||||
|
" case of force-publishing.",
|
||||||
)
|
)
|
||||||
list_supported_option = click.option(
|
list_supported_option = click.option(
|
||||||
"--list-platforms",
|
"--list-platforms",
|
||||||
|
@ -181,11 +177,19 @@ def pull():
|
||||||
help="Select an event with the current configured strategy"
|
help="Select an event with the current configured strategy"
|
||||||
" and publish it to all active platforms."
|
" and publish it to all active platforms."
|
||||||
)
|
)
|
||||||
@event_uuid_option
|
@force_publish_option
|
||||||
@publication_uuid_option
|
|
||||||
@platform_name_option
|
@platform_name_option
|
||||||
def publish():
|
@click.option(
|
||||||
safe_execution(publish_main,)
|
"--dry-run",
|
||||||
|
"dry_run",
|
||||||
|
is_flag=True,
|
||||||
|
help="Prevents data to be published to platforms.",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
def publish(event, platform, dry_run):
|
||||||
|
safe_execution(functools.partial(
|
||||||
|
publish_main, event, platform
|
||||||
|
), CommandConfig(dry_run=dry_run))
|
||||||
|
|
||||||
|
|
||||||
@mobilizon_reshare.group(help="Operations that pertain to events")
|
@mobilizon_reshare.group(help="Operations that pertain to events")
|
||||||
|
|
|
@ -24,7 +24,7 @@ def pretty(publication: Publication):
|
||||||
return (
|
return (
|
||||||
f"{str(publication.id) : <40}{publication.timestamp.isoformat() : <36}"
|
f"{str(publication.id) : <40}{publication.timestamp.isoformat() : <36}"
|
||||||
f"{click.style(publication.status.name, fg=status_to_color[publication.status]) : <22}"
|
f"{click.style(publication.status.name, fg=status_to_color[publication.status]) : <22}"
|
||||||
f"{publication.publisher.name : <12}{str(publication.event.id)}"
|
f"{publication.publisher.name : <12}{str(publication.event.mobilizon_id)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@ async def list_publications(
|
||||||
frm: Optional[datetime] = None,
|
frm: Optional[datetime] = None,
|
||||||
to: Optional[datetime] = None,
|
to: Optional[datetime] = None,
|
||||||
):
|
):
|
||||||
|
|
||||||
frm = Arrow.fromdatetime(frm) if frm else None
|
frm = Arrow.fromdatetime(frm) if frm else None
|
||||||
to = Arrow.fromdatetime(to) if to else None
|
to = Arrow.fromdatetime(to) if to else None
|
||||||
if status is None:
|
if status is None:
|
||||||
|
|
|
@ -1,14 +1,23 @@
|
||||||
import logging
|
import logging
|
||||||
|
import click
|
||||||
|
|
||||||
from mobilizon_reshare.main.publish import select_and_publish
|
from mobilizon_reshare.config.command import CommandConfig
|
||||||
|
from mobilizon_reshare.main.publish import select_and_publish, publish_by_mobilizon_id
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def publish_command():
|
async def publish_command(event_mobilizon_id: click.UUID, platform: str, command_config: CommandConfig):
|
||||||
"""
|
"""
|
||||||
Select an event with the current configured strategy
|
Select an event with the current configured strategy
|
||||||
and publish it to all active platforms.
|
and publish it to all active platforms.
|
||||||
"""
|
"""
|
||||||
report = await select_and_publish()
|
if event_mobilizon_id is not None:
|
||||||
|
report = await publish_by_mobilizon_id(
|
||||||
|
event_mobilizon_id,
|
||||||
|
command_config,
|
||||||
|
[platform] if platform is not None else None,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
report = await select_and_publish(command_config)
|
||||||
return 0 if report and report.successful else 1
|
return 0 if report and report.successful else 1
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import importlib.resources
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
|
from logging.config import dictConfig
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
from appdirs import AppDirs
|
from appdirs import AppDirs
|
||||||
from dynaconf import Dynaconf, Validator
|
from dynaconf import Dynaconf, Validator
|
||||||
|
|
||||||
|
@ -38,23 +38,30 @@ def current_version() -> str:
|
||||||
return fp.read()
|
return fp.read()
|
||||||
|
|
||||||
|
|
||||||
|
def init_logging(settings: Optional[Dynaconf] = None):
|
||||||
|
if settings is None:
|
||||||
|
settings = get_settings()
|
||||||
|
dictConfig(settings["logging"])
|
||||||
|
|
||||||
|
|
||||||
def get_settings_files_paths() -> Optional[str]:
|
def get_settings_files_paths() -> Optional[str]:
|
||||||
|
|
||||||
dirs = AppDirs(appname="mobilizon-reshare", version=current_version())
|
dirs = AppDirs(appname="mobilizon-reshare", version=current_version())
|
||||||
bundled_settings_path = pkg_resources.resource_filename(
|
bundled_settings_ref = importlib.resources.files(
|
||||||
"mobilizon_reshare", "settings.toml"
|
"mobilizon_reshare"
|
||||||
)
|
) / "settings.toml"
|
||||||
for config_path in [
|
with importlib.resources.as_file(bundled_settings_ref) as bundled_settings_path:
|
||||||
Path(dirs.user_config_dir, "mobilizon_reshare.toml").absolute(),
|
for config_path in [
|
||||||
Path(dirs.site_config_dir, "mobilizon_reshare.toml").absolute(),
|
Path(dirs.user_config_dir, "mobilizon_reshare.toml").absolute(),
|
||||||
bundled_settings_path,
|
Path(dirs.site_config_dir, "mobilizon_reshare.toml").absolute(),
|
||||||
]:
|
bundled_settings_path.absolute(),
|
||||||
if config_path and Path(config_path).exists():
|
]:
|
||||||
logger.debug(f"Loading configuration from {config_path}")
|
if config_path and Path(config_path).exists():
|
||||||
return config_path
|
logger.debug(f"Loading configuration from {config_path}")
|
||||||
|
return config_path
|
||||||
|
|
||||||
|
|
||||||
def build_settings(validators: Optional[list[Validator]] = None):
|
def build_settings(validators: Optional[list[Validator]] = None) -> Dynaconf:
|
||||||
"""
|
"""
|
||||||
Creates a Dynaconf base object. Configuration files are checked in this order:
|
Creates a Dynaconf base object. Configuration files are checked in this order:
|
||||||
|
|
||||||
|
@ -78,7 +85,7 @@ def build_settings(validators: Optional[list[Validator]] = None):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def build_and_validate_settings():
|
def build_and_validate_settings() -> Dynaconf:
|
||||||
"""
|
"""
|
||||||
Creates a settings object to be used in the application. It collects and apply generic validators and validators
|
Creates a settings object to be used in the application. It collects and apply generic validators and validators
|
||||||
specific for each publisher, notifier and publication strategy.
|
specific for each publisher, notifier and publication strategy.
|
||||||
|
@ -128,9 +135,9 @@ class CustomConfig:
|
||||||
cls._instance = None
|
cls._instance = None
|
||||||
|
|
||||||
|
|
||||||
def get_settings():
|
def get_settings() -> Dynaconf:
|
||||||
return CustomConfig.get_instance().settings
|
return CustomConfig.get_instance().settings
|
||||||
|
|
||||||
|
|
||||||
def get_settings_without_validation():
|
def get_settings_without_validation() -> Dynaconf:
|
||||||
return build_settings()
|
return build_settings()
|
||||||
|
|
|
@ -4,6 +4,7 @@ from dynaconf import Validator
|
||||||
|
|
||||||
telegram_validators = [
|
telegram_validators = [
|
||||||
Validator("notifier.telegram.chat_id", must_exist=True),
|
Validator("notifier.telegram.chat_id", must_exist=True),
|
||||||
|
Validator("notifier.telegram.message_thread_id", default=None),
|
||||||
Validator("notifier.telegram.token", must_exist=True),
|
Validator("notifier.telegram.token", must_exist=True),
|
||||||
Validator("notifier.telegram.username", must_exist=True),
|
Validator("notifier.telegram.username", must_exist=True),
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,6 +3,7 @@ from dynaconf import Validator
|
||||||
|
|
||||||
telegram_validators = [
|
telegram_validators = [
|
||||||
Validator("publisher.telegram.chat_id", must_exist=True),
|
Validator("publisher.telegram.chat_id", must_exist=True),
|
||||||
|
Validator("publisher.telegram.message_thread_id", default=None),
|
||||||
Validator("publisher.telegram.msg_template_path", must_exist=True, default=None),
|
Validator("publisher.telegram.msg_template_path", must_exist=True, default=None),
|
||||||
Validator("publisher.telegram.recap_template_path", must_exist=True, default=None),
|
Validator("publisher.telegram.recap_template_path", must_exist=True, default=None),
|
||||||
Validator(
|
Validator(
|
||||||
|
|
|
@ -2,8 +2,12 @@ from mobilizon_reshare.dataclasses.event import _MobilizonEvent
|
||||||
from mobilizon_reshare.dataclasses.event_publication_status import (
|
from mobilizon_reshare.dataclasses.event_publication_status import (
|
||||||
_EventPublicationStatus,
|
_EventPublicationStatus,
|
||||||
)
|
)
|
||||||
from mobilizon_reshare.dataclasses.publication import _EventPublication
|
from mobilizon_reshare.dataclasses.publication import (
|
||||||
|
_EventPublication,
|
||||||
|
_PublicationNotification,
|
||||||
|
)
|
||||||
|
|
||||||
EventPublication = _EventPublication
|
EventPublication = _EventPublication
|
||||||
MobilizonEvent = _MobilizonEvent
|
MobilizonEvent = _MobilizonEvent
|
||||||
EventPublicationStatus = _EventPublicationStatus
|
EventPublicationStatus = _EventPublicationStatus
|
||||||
|
PublicationNotification = _PublicationNotification
|
|
@ -108,7 +108,7 @@ class _MobilizonEvent:
|
||||||
async def get_all_mobilizon_events(
|
async def get_all_mobilizon_events(
|
||||||
from_date: Optional[Arrow] = None, to_date: Optional[Arrow] = None,
|
from_date: Optional[Arrow] = None, to_date: Optional[Arrow] = None,
|
||||||
) -> list[_MobilizonEvent]:
|
) -> list[_MobilizonEvent]:
|
||||||
return [_MobilizonEvent.from_model(event) for event in await get_all_events()]
|
return [_MobilizonEvent.from_model(event) for event in await get_all_events(from_date, to_date)]
|
||||||
|
|
||||||
|
|
||||||
async def get_published_events(
|
async def get_published_events(
|
||||||
|
@ -155,3 +155,10 @@ async def get_mobilizon_events_without_publications(
|
||||||
from_date=from_date, to_date=to_date
|
from_date=from_date, to_date=to_date
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def get_mobilizon_event_by_id(
|
||||||
|
event_id: UUID,
|
||||||
|
) -> _MobilizonEvent:
|
||||||
|
event = await get_event(event_id)
|
||||||
|
return _MobilizonEvent.from_model(event)
|
||||||
|
|
|
@ -54,6 +54,11 @@ class RecapPublication(BasePublication):
|
||||||
events: List[_MobilizonEvent]
|
events: List[_MobilizonEvent]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _PublicationNotification(BasePublication):
|
||||||
|
publication: _EventPublication
|
||||||
|
|
||||||
|
|
||||||
@atomic()
|
@atomic()
|
||||||
async def build_publications_for_event(
|
async def build_publications_for_event(
|
||||||
event: _MobilizonEvent, publishers: Iterator[str]
|
event: _MobilizonEvent, publishers: Iterator[str]
|
||||||
|
|
|
@ -6,6 +6,7 @@ from mobilizon_reshare.dataclasses import MobilizonEvent
|
||||||
from mobilizon_reshare.dataclasses.event import (
|
from mobilizon_reshare.dataclasses.event import (
|
||||||
get_published_events,
|
get_published_events,
|
||||||
get_mobilizon_events_without_publications,
|
get_mobilizon_events_without_publications,
|
||||||
|
get_mobilizon_event_by_id,
|
||||||
)
|
)
|
||||||
from mobilizon_reshare.dataclasses.publication import (
|
from mobilizon_reshare.dataclasses.publication import (
|
||||||
_EventPublication,
|
_EventPublication,
|
||||||
|
@ -23,7 +24,10 @@ from mobilizon_reshare.publishers.coordinators.event_publishing.publish import (
|
||||||
PublisherCoordinatorReport,
|
PublisherCoordinatorReport,
|
||||||
PublisherCoordinator,
|
PublisherCoordinator,
|
||||||
)
|
)
|
||||||
from mobilizon_reshare.storage.query.write import save_publication_report
|
from mobilizon_reshare.storage.query.write import (
|
||||||
|
save_publication_report,
|
||||||
|
save_notification_report,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -31,14 +35,16 @@ logger = logging.getLogger(__name__)
|
||||||
async def publish_publications(
|
async def publish_publications(
|
||||||
publications: list[_EventPublication],
|
publications: list[_EventPublication],
|
||||||
) -> PublisherCoordinatorReport:
|
) -> PublisherCoordinatorReport:
|
||||||
report = PublisherCoordinator(publications).run()
|
publishers_report = PublisherCoordinator(publications).run()
|
||||||
|
await save_publication_report(publishers_report)
|
||||||
|
|
||||||
await save_publication_report(report)
|
for publication_report in publishers_report.reports:
|
||||||
for publication_report in report.reports:
|
|
||||||
if not publication_report.successful:
|
if not publication_report.successful:
|
||||||
PublicationFailureNotifiersCoordinator(publication_report,).notify_failure()
|
notifiers_report = PublicationFailureNotifiersCoordinator(publication_report,).notify_failure()
|
||||||
|
if notifiers_report:
|
||||||
|
await save_notification_report(notifiers_report)
|
||||||
|
|
||||||
return report
|
return publishers_report
|
||||||
|
|
||||||
|
|
||||||
def perform_dry_run(publications: list[_EventPublication]):
|
def perform_dry_run(publications: list[_EventPublication]):
|
||||||
|
@ -63,6 +69,15 @@ async def publish_event(
|
||||||
return await publish_publications(publications)
|
return await publish_publications(publications)
|
||||||
|
|
||||||
|
|
||||||
|
async def publish_by_mobilizon_id(
|
||||||
|
event_mobilizon_id,
|
||||||
|
command_config: CommandConfig,
|
||||||
|
publishers: Optional[Iterator[str]] = None,
|
||||||
|
):
|
||||||
|
event = await get_mobilizon_event_by_id(event_mobilizon_id)
|
||||||
|
return await publish_event(event, command_config, publishers)
|
||||||
|
|
||||||
|
|
||||||
async def select_and_publish(
|
async def select_and_publish(
|
||||||
command_config: CommandConfig,
|
command_config: CommandConfig,
|
||||||
unpublished_events: Optional[list[MobilizonEvent]] = None,
|
unpublished_events: Optional[list[MobilizonEvent]] = None,
|
||||||
|
|
|
@ -5,10 +5,8 @@ from tortoise.models import Model
|
||||||
|
|
||||||
|
|
||||||
class NotificationStatus(IntEnum):
|
class NotificationStatus(IntEnum):
|
||||||
WAITING = 1
|
FAILED = 0
|
||||||
FAILED = 2
|
COMPLETED = 1
|
||||||
PARTIAL = 3
|
|
||||||
COMPLETED = 4
|
|
||||||
|
|
||||||
|
|
||||||
class Notification(Model):
|
class Notification(Model):
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
@ -124,6 +125,33 @@ class AbstractEventFormatter(LoggerMixin, ConfLoaderMixin):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError # pragma: no cover
|
raise NotImplementedError # pragma: no cover
|
||||||
|
|
||||||
|
def _get_name(self) -> str:
|
||||||
|
return self._conf[1]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_template(self, configured_template, default_generator) -> Template:
|
||||||
|
if configured_template:
|
||||||
|
return JINJA_ENV.get_template(configured_template)
|
||||||
|
else:
|
||||||
|
template_ref = default_generator()
|
||||||
|
with importlib.resources.as_file(template_ref) as template_path:
|
||||||
|
return JINJA_ENV.get_template(template_path.as_posix())
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_template_path(self, type=""):
|
||||||
|
return importlib.resources.files(
|
||||||
|
"mobilizon_reshare.publishers.templates"
|
||||||
|
) / f"{self._get_name()}{type}.tmpl.j2"
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_recap_template_path(self):
|
||||||
|
return self.get_default_template_path(type="_recap")
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_recap_header_template_path(self):
|
||||||
|
return self.get_default_template_path(type="_recap_header")
|
||||||
|
|
||||||
|
|
||||||
def validate_event(self, event: _MobilizonEvent) -> None:
|
def validate_event(self, event: _MobilizonEvent) -> None:
|
||||||
self._validate_event(event)
|
self._validate_event(event)
|
||||||
self._validate_message(self.get_message_from_event(event))
|
self._validate_message(self.get_message_from_event(event))
|
||||||
|
@ -148,21 +176,20 @@ class AbstractEventFormatter(LoggerMixin, ConfLoaderMixin):
|
||||||
"""
|
"""
|
||||||
Retrieves publisher's message template.
|
Retrieves publisher's message template.
|
||||||
"""
|
"""
|
||||||
template_path = self.conf.msg_template_path or self.default_template_path
|
return self._get_template(self.conf.msg_template_path, self.get_default_template_path)
|
||||||
return JINJA_ENV.get_template(template_path)
|
|
||||||
|
|
||||||
def get_recap_header(self):
|
def get_recap_header(self) -> Template:
|
||||||
template_path = (
|
return self._get_template(
|
||||||
self.conf.recap_header_template_path
|
self.conf.recap_header_template_path,
|
||||||
or self.default_recap_header_template_path
|
self.get_default_recap_header_template_path
|
||||||
)
|
)
|
||||||
return JINJA_ENV.get_template(template_path).render()
|
|
||||||
|
|
||||||
def get_recap_fragment_template(self) -> Template:
|
def get_recap_fragment_template(self) -> Template:
|
||||||
template_path = (
|
return self._get_template(
|
||||||
self.conf.recap_template_path or self.default_recap_template_path
|
self.conf.recap_template_path,
|
||||||
|
self.get_default_recap_template_path
|
||||||
)
|
)
|
||||||
return JINJA_ENV.get_template(template_path)
|
|
||||||
|
|
||||||
def get_recap_fragment(self, event: _MobilizonEvent) -> str:
|
def get_recap_fragment(self, event: _MobilizonEvent) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -16,7 +16,7 @@ class BasePublicationReport:
|
||||||
|
|
||||||
def get_failure_message(self):
|
def get_failure_message(self):
|
||||||
return (
|
return (
|
||||||
f"Publication failed with status: {self.status}.\n" f"Reason: {self.reason}"
|
f"Publication failed with status: {self.status.name}.\n" f"Reason: {self.reason}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class BaseCoordinatorReport:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def successful(self):
|
def successful(self):
|
||||||
return all(r.status == PublicationStatus.COMPLETED for r in self.reports)
|
return all(r.successful for r in self.reports)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
|
@ -20,7 +20,7 @@ class EventPublicationReport(BasePublicationReport):
|
||||||
logger.error("Report of failure without reason.", exc_info=True)
|
logger.error("Report of failure without reason.", exc_info=True)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
f"Publication {self.publication.id} failed with status: {self.status}.\n"
|
f"Publication {self.publication.id} failed with status: {self.status.name}.\n"
|
||||||
f"Reason: {self.reason}\n"
|
f"Reason: {self.reason}\n"
|
||||||
f"Publisher: {self.publication.publisher.name}\n"
|
f"Publisher: {self.publication.publisher.name}\n"
|
||||||
f"Event: {self.publication.event.name}"
|
f"Event: {self.publication.event.name}"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import logging
|
||||||
from typing import List, Sequence
|
from typing import List, Sequence
|
||||||
|
|
||||||
from mobilizon_reshare.dataclasses import _EventPublication
|
from mobilizon_reshare.dataclasses import _EventPublication
|
||||||
|
@ -7,6 +8,8 @@ from mobilizon_reshare.publishers.coordinators.event_publishing.publish import (
|
||||||
EventPublicationReport,
|
EventPublicationReport,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DryRunPublisherCoordinator(PublisherCoordinator):
|
class DryRunPublisherCoordinator(PublisherCoordinator):
|
||||||
"""
|
"""
|
||||||
|
@ -14,7 +17,7 @@ class DryRunPublisherCoordinator(PublisherCoordinator):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _publish(self, publications: Sequence[_EventPublication]) -> List[EventPublicationReport]:
|
def _publish(self, publications: Sequence[_EventPublication]) -> List[EventPublicationReport]:
|
||||||
return [
|
reports = [
|
||||||
EventPublicationReport(
|
EventPublicationReport(
|
||||||
status=PublicationStatus.COMPLETED,
|
status=PublicationStatus.COMPLETED,
|
||||||
publication=publication,
|
publication=publication,
|
||||||
|
@ -25,3 +28,9 @@ class DryRunPublisherCoordinator(PublisherCoordinator):
|
||||||
)
|
)
|
||||||
for publication in publications
|
for publication in publications
|
||||||
]
|
]
|
||||||
|
logger.info("The following events would be published:")
|
||||||
|
for r in reports:
|
||||||
|
event_name = r.publication.event.name
|
||||||
|
publisher_name = r.publication.publisher.name
|
||||||
|
logger.info(f"{event_name} → {publisher_name}")
|
||||||
|
return reports
|
||||||
|
|
|
@ -1,33 +1,92 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import List
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Optional, Sequence
|
||||||
|
|
||||||
|
from mobilizon_reshare.dataclasses import PublicationNotification, EventPublication
|
||||||
|
from mobilizon_reshare.models.notification import NotificationStatus
|
||||||
from mobilizon_reshare.models.publication import PublicationStatus
|
from mobilizon_reshare.models.publication import PublicationStatus
|
||||||
from mobilizon_reshare.publishers import get_active_notifiers
|
from mobilizon_reshare.publishers import get_active_notifiers
|
||||||
from mobilizon_reshare.publishers.abstract import AbstractPlatform
|
from mobilizon_reshare.publishers.abstract import (
|
||||||
from mobilizon_reshare.publishers.coordinators import logger
|
AbstractPlatform,
|
||||||
from mobilizon_reshare.publishers.coordinators.event_publishing.publish import (
|
)
|
||||||
|
from mobilizon_reshare.publishers.coordinators import (
|
||||||
|
logger,
|
||||||
|
BasePublicationReport,
|
||||||
|
BaseCoordinatorReport,
|
||||||
|
)
|
||||||
|
from mobilizon_reshare.publishers.coordinators.event_publishing import (
|
||||||
EventPublicationReport,
|
EventPublicationReport,
|
||||||
)
|
)
|
||||||
from mobilizon_reshare.publishers.platforms.platform_mapping import get_notifier_class
|
from mobilizon_reshare.publishers.platforms.platform_mapping import (
|
||||||
|
get_notifier_class,
|
||||||
|
get_formatter_class,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PublicationNotificationReport(BasePublicationReport):
|
||||||
|
status: NotificationStatus
|
||||||
|
notification: PublicationNotification
|
||||||
|
|
||||||
|
@property
|
||||||
|
def successful(self):
|
||||||
|
return self.status == NotificationStatus.COMPLETED
|
||||||
|
|
||||||
|
def get_failure_message(self):
|
||||||
|
if not self.reason:
|
||||||
|
logger.error("Report of failure without reason.", exc_info=True)
|
||||||
|
return (
|
||||||
|
f"Failed with status: {self.status.name}.\n"
|
||||||
|
f"Reason: {self.reason}\n"
|
||||||
|
f"Publisher: {self.notification.publisher.name}\n"
|
||||||
|
f"Publication: {self.notification.publication.id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class NotifierCoordinatorReport(BaseCoordinatorReport):
|
||||||
|
reports: Sequence[PublicationNotificationReport]
|
||||||
|
notifications: Sequence[PublicationNotification] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class Sender:
|
class Sender:
|
||||||
def __init__(self, message: str, platforms: List[AbstractPlatform] = None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
publication: EventPublication,
|
||||||
|
platforms: List[AbstractPlatform] = None,
|
||||||
|
):
|
||||||
self.message = message
|
self.message = message
|
||||||
self.platforms = platforms
|
self.platforms = platforms
|
||||||
|
self.publication = publication
|
||||||
|
|
||||||
def send_to_all(self):
|
def send_to_all(self) -> NotifierCoordinatorReport:
|
||||||
|
reports = []
|
||||||
|
notifications = []
|
||||||
for platform in self.platforms:
|
for platform in self.platforms:
|
||||||
|
notification = PublicationNotification(
|
||||||
|
platform, get_formatter_class(platform.name)(), self.publication
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
platform.send(self.message)
|
platform.send(self.message)
|
||||||
|
report = PublicationNotificationReport(
|
||||||
|
NotificationStatus.COMPLETED, self.message, notification
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(f"Failed to send message:\n{self.message}")
|
msg = f"[{platform.name}] Failed to notify failure of message:\n{self.message}"
|
||||||
|
logger.critical(msg)
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
report = PublicationNotificationReport(
|
||||||
|
NotificationStatus.FAILED, msg, notification
|
||||||
|
)
|
||||||
|
notifications.append(notification)
|
||||||
|
reports.append(report)
|
||||||
|
return NotifierCoordinatorReport(reports=reports, notifications=notifications)
|
||||||
|
|
||||||
|
|
||||||
class AbstractNotifiersCoordinator(ABC):
|
class AbstractNotifiersCoordinator(ABC):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, report: EventPublicationReport, notifiers: List[AbstractPlatform] = None
|
self, report: BasePublicationReport, notifiers: List[AbstractPlatform] = None
|
||||||
):
|
):
|
||||||
self.platforms = notifiers or [
|
self.platforms = notifiers or [
|
||||||
get_notifier_class(notifier)() for notifier in get_active_notifiers()
|
get_notifier_class(notifier)() for notifier in get_active_notifiers()
|
||||||
|
@ -44,10 +103,17 @@ class PublicationFailureNotifiersCoordinator(AbstractNotifiersCoordinator):
|
||||||
Sends a notification of a failure report to the active platforms
|
Sends a notification of a failure report to the active platforms
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def notify_failure(self):
|
report: EventPublicationReport
|
||||||
|
platforms: List[AbstractPlatform]
|
||||||
|
|
||||||
|
def notify_failure(self) -> Optional[NotifierCoordinatorReport]:
|
||||||
logger.info("Sending failure notifications")
|
logger.info("Sending failure notifications")
|
||||||
if self.report.status == PublicationStatus.FAILED:
|
if self.report.status == PublicationStatus.FAILED:
|
||||||
Sender(self.report.get_failure_message(), self.platforms).send_to_all()
|
return Sender(
|
||||||
|
self.report.get_failure_message(),
|
||||||
|
self.report.publication,
|
||||||
|
self.platforms,
|
||||||
|
).send_to_all()
|
||||||
|
|
||||||
|
|
||||||
class PublicationFailureLoggerCoordinator(PublicationFailureNotifiersCoordinator):
|
class PublicationFailureLoggerCoordinator(PublicationFailureNotifiersCoordinator):
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import facebook
|
import facebook
|
||||||
import pkg_resources
|
|
||||||
from facebook import GraphAPIError
|
from facebook import GraphAPIError
|
||||||
|
|
||||||
from mobilizon_reshare.dataclasses import MobilizonEvent
|
from mobilizon_reshare.dataclasses import MobilizonEvent
|
||||||
|
@ -19,19 +18,7 @@ from mobilizon_reshare.publishers.exceptions import (
|
||||||
|
|
||||||
|
|
||||||
class FacebookFormatter(AbstractEventFormatter):
|
class FacebookFormatter(AbstractEventFormatter):
|
||||||
|
|
||||||
_conf = ("publisher", "facebook")
|
_conf = ("publisher", "facebook")
|
||||||
default_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "facebook.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
default_recap_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "facebook_recap.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
default_recap_header_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "facebook_recap_header.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_event(self, event: MobilizonEvent) -> None:
|
def _validate_event(self, event: MobilizonEvent) -> None:
|
||||||
text = event.description
|
text = event.description
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
import requests
|
import requests
|
||||||
from requests import Response
|
from requests import Response
|
||||||
|
|
||||||
|
@ -20,19 +19,7 @@ from mobilizon_reshare.publishers.exceptions import (
|
||||||
|
|
||||||
|
|
||||||
class MastodonFormatter(AbstractEventFormatter):
|
class MastodonFormatter(AbstractEventFormatter):
|
||||||
|
|
||||||
_conf = ("publisher", "mastodon")
|
_conf = ("publisher", "mastodon")
|
||||||
default_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "mastodon.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
default_recap_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "mastodon_recap.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
default_recap_header_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "mastodon_recap_header.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_event(self, event: MobilizonEvent) -> None:
|
def _validate_event(self, event: MobilizonEvent) -> None:
|
||||||
text = event.description
|
text = event.description
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import re
|
import re
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from requests import Response
|
from requests import Response
|
||||||
|
@ -20,18 +19,6 @@ from mobilizon_reshare.publishers.exceptions import (
|
||||||
|
|
||||||
|
|
||||||
class TelegramFormatter(AbstractEventFormatter):
|
class TelegramFormatter(AbstractEventFormatter):
|
||||||
default_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "telegram.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
default_recap_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "telegram_recap.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
default_recap_header_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "telegram_recap_header.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
_conf = ("publisher", "telegram")
|
_conf = ("publisher", "telegram")
|
||||||
|
|
||||||
def _validate_event(self, event: MobilizonEvent) -> None:
|
def _validate_event(self, event: MobilizonEvent) -> None:
|
||||||
|
@ -99,9 +86,14 @@ class TelegramPlatform(AbstractPlatform):
|
||||||
)
|
)
|
||||||
|
|
||||||
def _send(self, message: str, event: Optional[MobilizonEvent] = None) -> Response:
|
def _send(self, message: str, event: Optional[MobilizonEvent] = None) -> Response:
|
||||||
|
json_message = {"chat_id": self.conf.chat_id, "text": message, "parse_mode": "html"}
|
||||||
|
|
||||||
|
if self.conf.message_thread_id:
|
||||||
|
json_message["message_thread_id"] = self.conf.message_thread_id
|
||||||
|
|
||||||
return requests.post(
|
return requests.post(
|
||||||
url=f"https://api.telegram.org/bot{self.conf.token}/sendMessage",
|
url=f"https://api.telegram.org/bot{self.conf.token}/sendMessage",
|
||||||
json={"chat_id": self.conf.chat_id, "text": message, "parse_mode": "html"},
|
json=json_message,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _validate_response(self, res):
|
def _validate_response(self, res):
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
from tweepy import OAuthHandler, API, TweepyException
|
from tweepy import OAuthHandler, API, TweepyException
|
||||||
from tweepy.models import Status
|
from tweepy.models import Status
|
||||||
|
|
||||||
|
@ -17,19 +16,7 @@ from mobilizon_reshare.publishers.exceptions import (
|
||||||
|
|
||||||
|
|
||||||
class TwitterFormatter(AbstractEventFormatter):
|
class TwitterFormatter(AbstractEventFormatter):
|
||||||
|
|
||||||
_conf = ("publisher", "twitter")
|
_conf = ("publisher", "twitter")
|
||||||
default_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "twitter.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
default_recap_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "twitter_recap.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
default_recap_header_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "twitter_recap_header.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_event(self, event: MobilizonEvent) -> None:
|
def _validate_event(self, event: MobilizonEvent) -> None:
|
||||||
pass # pragma: no cover
|
pass # pragma: no cover
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
import requests
|
import requests
|
||||||
from requests import Response
|
from requests import Response
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
|
@ -23,19 +22,7 @@ from mobilizon_reshare.publishers.exceptions import (
|
||||||
|
|
||||||
|
|
||||||
class ZulipFormatter(AbstractEventFormatter):
|
class ZulipFormatter(AbstractEventFormatter):
|
||||||
|
|
||||||
_conf = ("publisher", "zulip")
|
_conf = ("publisher", "zulip")
|
||||||
default_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "zulip.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
default_recap_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "zulip_recap.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
default_recap_header_template_path = pkg_resources.resource_filename(
|
|
||||||
"mobilizon_reshare.publishers.templates", "zulip_recap_header.tmpl.j2"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_event(self, event: MobilizonEvent) -> None:
|
def _validate_event(self, event: MobilizonEvent) -> None:
|
||||||
text = event.description
|
text = event.description
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[default]
|
[default]
|
||||||
local_state_dir = "/var/mobilizon_reshare"
|
local_state_dir = "/var/mobilizon_reshare"
|
||||||
db_url = "sqlite:///var/mobilizon_reshare/events.db"
|
log_dir = "@format {this.local_state_dir}"
|
||||||
|
db_url = "@format sqlite://{this.local_state_dir}/events.db"
|
||||||
locale= "en-us"
|
locale= "en-us"
|
||||||
|
|
||||||
[default.source.mobilizon]
|
[default.source.mobilizon]
|
||||||
|
@ -31,7 +32,7 @@ stream = "ext://sys.stderr"
|
||||||
level = "DEBUG"
|
level = "DEBUG"
|
||||||
class = "logging.handlers.RotatingFileHandler"
|
class = "logging.handlers.RotatingFileHandler"
|
||||||
formatter = "standard"
|
formatter = "standard"
|
||||||
filename = "/var/log/mobilizon_reshare/mobilizon_reshare.log"
|
filename = "@format {this.log_dir}/mobilizon_reshare.log"
|
||||||
maxBytes = 52428800
|
maxBytes = 52428800
|
||||||
backupCount = 500
|
backupCount = 500
|
||||||
encoding = "utf8"
|
encoding = "utf8"
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
from logging.config import dictConfig
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pkg_resources
|
import importlib
|
||||||
import urllib3.util
|
import urllib3.util
|
||||||
from aerich import Command
|
from aerich import Command
|
||||||
from tortoise import Tortoise
|
from tortoise import Tortoise
|
||||||
|
@ -48,9 +47,9 @@ TORTOISE_ORM = get_tortoise_orm()
|
||||||
class MoReDB:
|
class MoReDB:
|
||||||
def get_migration_location(self):
|
def get_migration_location(self):
|
||||||
scheme = get_db_url().scheme
|
scheme = get_db_url().scheme
|
||||||
return pkg_resources.resource_filename(
|
scheme_ref = importlib.resources.files("mobilizon_reshare") / "migrations" / f"{scheme}"
|
||||||
"mobilizon_reshare", f"migrations/{scheme}"
|
with importlib.resources.as_file(scheme_ref) as scheme_path:
|
||||||
)
|
return scheme_path
|
||||||
|
|
||||||
async def _implement_db_changes(self):
|
async def _implement_db_changes(self):
|
||||||
logging.info("Performing aerich migrations.")
|
logging.info("Performing aerich migrations.")
|
||||||
|
@ -92,11 +91,7 @@ async def tear_down():
|
||||||
return await Tortoise.close_connections()
|
return await Tortoise.close_connections()
|
||||||
|
|
||||||
|
|
||||||
async def init(init_logging=True):
|
async def init():
|
||||||
|
|
||||||
if init_logging:
|
|
||||||
dictConfig(get_settings()["logging"])
|
|
||||||
|
|
||||||
# init storage
|
# init storage
|
||||||
url = get_db_url()
|
url = get_db_url()
|
||||||
if url.scheme == "sqlite":
|
if url.scheme == "sqlite":
|
||||||
|
|
|
@ -33,7 +33,7 @@ async def get_all_publishers() -> list[Publisher]:
|
||||||
|
|
||||||
async def prefetch_event_relations(queryset: QuerySet[Event]) -> list[Event]:
|
async def prefetch_event_relations(queryset: QuerySet[Event]) -> list[Event]:
|
||||||
return (
|
return (
|
||||||
await queryset.prefetch_related("publications__publisher")
|
await queryset.prefetch_related("publications__publisher", "publications__notifications")
|
||||||
.order_by("begin_datetime")
|
.order_by("begin_datetime")
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
@ -46,6 +46,7 @@ async def prefetch_publication_relations(
|
||||||
await queryset.prefetch_related(
|
await queryset.prefetch_related(
|
||||||
"publisher",
|
"publisher",
|
||||||
"event",
|
"event",
|
||||||
|
"notifications",
|
||||||
"event__publications",
|
"event__publications",
|
||||||
"event__publications__publisher",
|
"event__publications__publisher",
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,11 +9,15 @@ from mobilizon_reshare.dataclasses.event import (
|
||||||
get_mobilizon_events_without_publications,
|
get_mobilizon_events_without_publications,
|
||||||
)
|
)
|
||||||
from mobilizon_reshare.models.event import Event
|
from mobilizon_reshare.models.event import Event
|
||||||
|
from mobilizon_reshare.models.notification import Notification
|
||||||
from mobilizon_reshare.models.publication import Publication
|
from mobilizon_reshare.models.publication import Publication
|
||||||
from mobilizon_reshare.models.publisher import Publisher
|
from mobilizon_reshare.models.publisher import Publisher
|
||||||
from mobilizon_reshare.publishers.coordinators.event_publishing import (
|
from mobilizon_reshare.publishers.coordinators.event_publishing import (
|
||||||
EventPublicationReport,
|
EventPublicationReport,
|
||||||
)
|
)
|
||||||
|
from mobilizon_reshare.publishers.coordinators.event_publishing.notify import (
|
||||||
|
NotifierCoordinatorReport,
|
||||||
|
)
|
||||||
from mobilizon_reshare.publishers.coordinators.event_publishing.publish import (
|
from mobilizon_reshare.publishers.coordinators.event_publishing.publish import (
|
||||||
PublisherCoordinatorReport,
|
PublisherCoordinatorReport,
|
||||||
)
|
)
|
||||||
|
@ -64,6 +68,24 @@ async def save_publication_report(
|
||||||
await upsert_publication(publication_report, event)
|
await upsert_publication(publication_report, event)
|
||||||
|
|
||||||
|
|
||||||
|
@atomic()
|
||||||
|
async def save_notification_report(
|
||||||
|
coordinator_report: NotifierCoordinatorReport,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Store a notification process outcome
|
||||||
|
"""
|
||||||
|
for report in coordinator_report.reports:
|
||||||
|
publisher = await Publisher.filter(name=report.notification.publisher.name).first()
|
||||||
|
|
||||||
|
await Notification.create(
|
||||||
|
publication_id=report.notification.publication.id,
|
||||||
|
target_id=publisher.id,
|
||||||
|
status=report.status,
|
||||||
|
message=report.reason,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@atomic()
|
@atomic()
|
||||||
async def create_unpublished_events(
|
async def create_unpublished_events(
|
||||||
events_from_mobilizon: Iterable[MobilizonEvent],
|
events_from_mobilizon: Iterable[MobilizonEvent],
|
||||||
|
|
|
@ -3,6 +3,7 @@ import logging
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi_pagination import add_pagination
|
from fastapi_pagination import add_pagination
|
||||||
|
|
||||||
|
from mobilizon_reshare.config.config import init_logging as init_log
|
||||||
from mobilizon_reshare.storage.db import init as init_db, get_db_url
|
from mobilizon_reshare.storage.db import init as init_db, get_db_url
|
||||||
from mobilizon_reshare.web.backend.events.endpoints import (
|
from mobilizon_reshare.web.backend.events.endpoints import (
|
||||||
register_endpoints as register_event_endpoints,
|
register_endpoints as register_event_endpoints,
|
||||||
|
@ -38,7 +39,9 @@ def init_endpoints(app):
|
||||||
|
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
async def init_app(init_logging=True):
|
async def init_app(init_logging=True):
|
||||||
|
if init_logging:
|
||||||
|
init_log()
|
||||||
check_database()
|
check_database()
|
||||||
await init_db(init_logging=init_logging)
|
await init_db()
|
||||||
init_endpoints(app)
|
init_endpoints(app)
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -30,28 +30,30 @@ typing_extensions = ">=3.7.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alabaster"
|
name = "alabaster"
|
||||||
version = "0.7.13"
|
version = "0.7.16"
|
||||||
description = "A configurable sidebar-enabled Sphinx theme"
|
description = "A light, configurable Sphinx theme"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
version = "3.6.2"
|
version = "4.3.0"
|
||||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6.2"
|
python-versions = ">=3.8"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
|
||||||
idna = ">=2.8"
|
idna = ">=2.8"
|
||||||
sniffio = ">=1.1"
|
sniffio = ">=1.1"
|
||||||
|
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
|
doc = ["packaging", "Sphinx (>=7)", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
|
||||||
test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"]
|
test = ["anyio", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||||
trio = ["trio (>=0.16,<0.22)"]
|
trio = ["trio (>=0.23)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "appdirs"
|
name = "appdirs"
|
||||||
|
@ -73,28 +75,27 @@ python-versions = ">=3.6"
|
||||||
python-dateutil = ">=2.7.0"
|
python-dateutil = ">=2.7.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "asgiref"
|
name = "async-timeout"
|
||||||
version = "3.6.0"
|
version = "4.0.3"
|
||||||
description = "ASGI specs, helper code, and adapters"
|
description = "Timeout context manager for asyncio programs"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "asyncpg"
|
name = "asyncpg"
|
||||||
version = "0.27.0"
|
version = "0.29.0"
|
||||||
description = "An asyncio PostgreSQL driver"
|
description = "An asyncio PostgreSQL driver"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7.0"
|
python-versions = ">=3.8.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "flake8 (>=5.0.4,<5.1.0)", "uvloop (>=0.15.3)"]
|
docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=1.2.2)"]
|
||||||
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"]
|
test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"]
|
||||||
test = ["flake8 (>=5.0.4,<5.1.0)", "uvloop (>=0.15.3)"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "asynctest"
|
name = "asynctest"
|
||||||
|
@ -114,7 +115,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
version = "23.1.0"
|
version = "23.2.0"
|
||||||
description = "Classes Without Boilerplate"
|
description = "Classes Without Boilerplate"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -125,16 +126,20 @@ cov = ["attrs", "coverage[toml] (>=5.3)"]
|
||||||
dev = ["attrs", "pre-commit"]
|
dev = ["attrs", "pre-commit"]
|
||||||
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
|
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
|
||||||
tests = ["attrs", "zope-interface"]
|
tests = ["attrs", "zope-interface"]
|
||||||
tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest-mypy-plugins", "pytest-xdist", "pytest (>=4.3.0)"]
|
tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"]
|
||||||
|
tests-no-zope = ["attrs", "cloudpickle", "hypothesis", "pympler", "pytest-xdist", "pytest (>=4.3.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "babel"
|
name = "babel"
|
||||||
version = "2.12.1"
|
version = "2.14.0"
|
||||||
description = "Internationalization utilities"
|
description = "Internationalization utilities"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pytest (>=6.0)", "pytest-cov", "freezegun (>=1.0,<2.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "beautifulsoup4"
|
name = "beautifulsoup4"
|
||||||
version = "4.11.2"
|
version = "4.11.2"
|
||||||
|
@ -152,7 +157,7 @@ lxml = ["lxml"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2023.5.7"
|
version = "2024.2.2"
|
||||||
description = "Python package for providing Mozilla's CA Bundle."
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -160,7 +165,7 @@ python-versions = ">=3.6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "charset-normalizer"
|
name = "charset-normalizer"
|
||||||
version = "3.1.0"
|
version = "3.3.2"
|
||||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -168,7 +173,7 @@ python-versions = ">=3.7.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.1.3"
|
version = "8.1.7"
|
||||||
description = "Composable command line interface toolkit"
|
description = "Composable command line interface toolkit"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -187,11 +192,11 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coverage"
|
name = "coverage"
|
||||||
version = "7.2.5"
|
version = "7.4.3"
|
||||||
description = "Code coverage measurement for Python"
|
description = "Code coverage measurement for Python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
||||||
|
@ -247,6 +252,17 @@ toml = ["toml"]
|
||||||
vault = ["hvac"]
|
vault = ["hvac"]
|
||||||
yaml = ["ruamel.yaml"]
|
yaml = ["ruamel.yaml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exceptiongroup"
|
||||||
|
version = "1.2.0"
|
||||||
|
description = "Backport of PEP 654 (exception groups)"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["pytest (>=6)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "facebook-sdk"
|
name = "facebook-sdk"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
|
@ -260,7 +276,7 @@ requests = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi"
|
name = "fastapi"
|
||||||
version = "0.85.2"
|
version = "0.92.0"
|
||||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -268,17 +284,17 @@ python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
|
pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
|
||||||
starlette = "0.20.4"
|
starlette = ">=0.25.0,<0.26.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["email-validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.19.0)"]
|
all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||||
dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "uvicorn[standard] (>=0.12.0,<0.19.0)"]
|
dev = ["pre-commit (>=2.17.0,<3.0.0)", "ruff (==0.0.138)", "uvicorn[standard] (>=0.12.0,<0.21.0)"]
|
||||||
doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer[all] (>=0.6.1,<0.7.0)"]
|
doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer[all] (>=0.6.1,<0.8.0)"]
|
||||||
test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.982)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest-cov (>=2.12.0,<5.0.0)", "pytest (>=7.1.3,<8.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<=1.4.41)", "types-orjson (==3.6.2)", "types-ujson (==5.5.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"]
|
test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.10.0)", "coverage[toml] (>=6.5.0,<8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.982)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "ruff (==0.0.138)", "sqlalchemy (>=1.3.18,<1.4.43)", "types-orjson (==3.6.2)", "types-ujson (==5.6.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi-pagination"
|
name = "fastapi-pagination"
|
||||||
version = "0.11.4"
|
version = "0.12.3"
|
||||||
description = "FastAPI pagination"
|
description = "FastAPI pagination"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -289,20 +305,21 @@ fastapi = ">=0.80.0"
|
||||||
pydantic = ">=1.9.1"
|
pydantic = ">=1.9.1"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
sqlalchemy = ["SQLAlchemy (>=1.3.20)", "sqlakeyset (>=1.0.1659142803,<2.0.0)"]
|
sqlalchemy = ["SQLAlchemy (>=1.3.20)", "sqlakeyset (>=2.0.1680321678,<3.0.0)"]
|
||||||
asyncpg = ["SQLAlchemy (>=1.3.20)", "asyncpg (>=0.24.0)"]
|
asyncpg = ["SQLAlchemy (>=1.3.20)", "asyncpg (>=0.24.0)"]
|
||||||
all = ["SQLAlchemy (>=1.3.20)", "databases (>=0.6.0)", "orm (>=0.3.1)", "tortoise-orm (>=0.16.18,<0.20.0)", "asyncpg (>=0.24.0)", "ormar (>=0.11.2)", "django (<5.0.0)", "piccolo (>=0.89,<0.106)", "motor (>=2.5.1,<4.0.0)", "mongoengine (>=0.23.1,<0.27.0)", "sqlmodel (>=0.0.8,<0.0.9)", "pony (>=0.7.16,<0.8.0)", "beanie (>=1.11.9,<2.0.0)", "sqlakeyset (>=1.0.1659142803,<2.0.0)", "scylla-driver (>=3.25.6,<4.0.0)"]
|
all = ["SQLAlchemy (>=1.3.20)", "databases (>=0.6.0)", "orm (>=0.3.1)", "tortoise-orm (>=0.16.18,<0.20.0)", "asyncpg (>=0.24.0)", "ormar (>=0.11.2)", "django (<5.0.0)", "piccolo (>=0.89,<0.112)", "motor (>=2.5.1,<4.0.0)", "mongoengine (>=0.23.1,<0.28.0)", "sqlmodel (>=0.0.8,<0.0.9)", "pony (>=0.7.16,<0.8.0)", "beanie (>=1.11.9,<2.0.0)", "sqlakeyset (>=2.0.1680321678,<3.0.0)", "scylla-driver (>=3.25.6,<4.0.0)", "bunnet (>=1.1.0,<2.0.0)"]
|
||||||
databases = ["databases (>=0.6.0)"]
|
databases = ["databases (>=0.6.0)"]
|
||||||
orm = ["databases (>=0.6.0)", "orm (>=0.3.1)"]
|
orm = ["databases (>=0.6.0)", "orm (>=0.3.1)"]
|
||||||
django = ["databases (>=0.6.0)", "django (<5.0.0)"]
|
django = ["databases (>=0.6.0)", "django (<5.0.0)"]
|
||||||
tortoise = ["tortoise-orm (>=0.16.18,<0.20.0)"]
|
tortoise = ["tortoise-orm (>=0.16.18,<0.20.0)"]
|
||||||
ormar = ["ormar (>=0.11.2)"]
|
ormar = ["ormar (>=0.11.2)"]
|
||||||
piccolo = ["piccolo (>=0.89,<0.106)"]
|
piccolo = ["piccolo (>=0.89,<0.112)"]
|
||||||
motor = ["motor (>=2.5.1,<4.0.0)"]
|
motor = ["motor (>=2.5.1,<4.0.0)"]
|
||||||
mongoengine = ["mongoengine (>=0.23.1,<0.27.0)"]
|
mongoengine = ["mongoengine (>=0.23.1,<0.28.0)"]
|
||||||
sqlmodel = ["sqlmodel (>=0.0.8,<0.0.9)", "sqlakeyset (>=1.0.1659142803,<2.0.0)"]
|
sqlmodel = ["sqlmodel (>=0.0.8,<0.0.9)", "sqlakeyset (>=2.0.1680321678,<3.0.0)"]
|
||||||
beanie = ["beanie (>=1.11.9,<2.0.0)"]
|
beanie = ["beanie (>=1.11.9,<2.0.0)"]
|
||||||
scylla-driver = ["scylla-driver (>=3.25.6,<4.0.0)"]
|
scylla-driver = ["scylla-driver (>=3.25.6,<4.0.0)"]
|
||||||
|
bunnet = ["bunnet (>=1.1.0,<2.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h11"
|
name = "h11"
|
||||||
|
@ -314,7 +331,7 @@ python-versions = ">=3.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpcore"
|
name = "httpcore"
|
||||||
version = "0.16.3"
|
version = "0.17.3"
|
||||||
description = "A minimal low-level HTTP client."
|
description = "A minimal low-level HTTP client."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -332,7 +349,7 @@ socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpx"
|
name = "httpx"
|
||||||
version = "0.23.3"
|
version = "0.24.1"
|
||||||
description = "The next generation HTTP client."
|
description = "The next generation HTTP client."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -340,19 +357,19 @@ python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
certifi = "*"
|
certifi = "*"
|
||||||
httpcore = ">=0.15.0,<0.17.0"
|
httpcore = ">=0.15.0,<0.18.0"
|
||||||
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
|
idna = "*"
|
||||||
sniffio = "*"
|
sniffio = "*"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
brotli = ["brotli", "brotlicffi"]
|
brotli = ["brotli", "brotlicffi"]
|
||||||
cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"]
|
cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"]
|
||||||
http2 = ["h2 (>=3,<5)"]
|
http2 = ["h2 (>=3,<5)"]
|
||||||
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.4"
|
version = "3.6"
|
||||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -366,22 +383,6 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "importlib-metadata"
|
|
||||||
version = "6.6.0"
|
|
||||||
description = "Read metadata from Python packages"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
zipp = ">=0.5"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"]
|
|
||||||
perf = ["ipython"]
|
|
||||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8", "importlib-resources (>=1.3)"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
@ -400,7 +401,7 @@ python-versions = ">=3.6.2,<4.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.1.2"
|
version = "3.1.3"
|
||||||
description = "A very fast and expressive template engine."
|
description = "A very fast and expressive template engine."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -414,21 +415,21 @@ i18n = ["Babel (>=2.7)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lxml"
|
name = "lxml"
|
||||||
version = "4.9.2"
|
version = "5.1.0"
|
||||||
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
|
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
cssselect = ["cssselect (>=0.7)"]
|
cssselect = ["cssselect (>=0.7)"]
|
||||||
html5 = ["html5lib"]
|
html5 = ["html5lib"]
|
||||||
htmlsoup = ["beautifulsoup4"]
|
htmlsoup = ["beautifulsoup4"]
|
||||||
source = ["Cython (>=0.29.7)"]
|
source = ["Cython (>=3.0.7)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markdownify"
|
name = "markdownify"
|
||||||
version = "0.10.3"
|
version = "0.11.6"
|
||||||
description = "Convert HTML to markdown."
|
description = "Convert HTML to markdown."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -440,7 +441,7 @@ six = ">=1.15,<2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markupsafe"
|
name = "markupsafe"
|
||||||
version = "2.1.2"
|
version = "2.1.5"
|
||||||
description = "Safely add untrusted strings to HTML/XML markup."
|
description = "Safely add untrusted strings to HTML/XML markup."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -461,7 +462,7 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "23.1"
|
version = "23.2"
|
||||||
description = "Core utilities for Python packages"
|
description = "Core utilities for Python packages"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -469,11 +470,11 @@ python-versions = ">=3.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pluggy"
|
name = "pluggy"
|
||||||
version = "1.0.0"
|
version = "1.4.0"
|
||||||
description = "plugin and hook calling mechanisms for python"
|
description = "plugin and hook calling mechanisms for python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.8"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["pre-commit", "tox"]
|
dev = ["pre-commit", "tox"]
|
||||||
|
@ -500,7 +501,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "1.10.7"
|
version = "1.10.14"
|
||||||
description = "Data validation and settings management using python type hints"
|
description = "Data validation and settings management using python type hints"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -515,7 +516,7 @@ email = ["email-validator (>=1.0.3)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.15.1"
|
version = "2.17.2"
|
||||||
description = "Pygments is a syntax highlighting package written in Python."
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -523,6 +524,7 @@ python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
plugins = ["importlib-metadata"]
|
plugins = ["importlib-metadata"]
|
||||||
|
windows-terminal = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pypika-tortoise"
|
name = "pypika-tortoise"
|
||||||
|
@ -595,7 +597,7 @@ pytest = ">=3.2.5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
version = "2.8.2"
|
version = "2.9.0.post0"
|
||||||
description = "Extensions to the standard Python datetime module"
|
description = "Extensions to the standard Python datetime module"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -606,7 +608,7 @@ six = ">=1.5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-slugify"
|
name = "python-slugify"
|
||||||
version = "8.0.1"
|
version = "8.0.4"
|
||||||
description = "A Python slugify application that also handles Unicode"
|
description = "A Python slugify application that also handles Unicode"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -621,7 +623,7 @@ unidecode = ["Unidecode (>=1.1.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2023.3"
|
version = "2024.1"
|
||||||
description = "World timezone definitions, modern and historical"
|
description = "World timezone definitions, modern and historical"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -662,33 +664,20 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "responses"
|
name = "responses"
|
||||||
version = "0.13.4"
|
version = "0.22.0"
|
||||||
description = "A utility library for mocking out the `requests` Python library."
|
description = "A utility library for mocking out the `requests` Python library."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
requests = ">=2.0"
|
requests = ">=2.22.0,<3.0"
|
||||||
six = "*"
|
toml = "*"
|
||||||
|
types-toml = "*"
|
||||||
urllib3 = ">=1.25.10"
|
urllib3 = ">=1.25.10"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["coverage (>=3.7.1,<6.0.0)", "pytest-cov", "pytest-localserver", "flake8", "types-mock", "types-requests", "types-six", "pytest (>=4.6,<5.0)", "pytest (>=4.6)", "mypy"]
|
tests = ["pytest (>=7.0.0)", "coverage (>=6.0.0)", "pytest-cov", "pytest-asyncio", "pytest-httpserver", "flake8", "types-requests", "mypy"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rfc3986"
|
|
||||||
version = "1.5.0"
|
|
||||||
description = "Validating URI References per RFC 3986"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
idna = {version = "*", optional = true, markers = "extra == \"idna2008\""}
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
idna2008 = ["idna"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "six"
|
name = "six"
|
||||||
|
@ -700,7 +689,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sniffio"
|
name = "sniffio"
|
||||||
version = "1.3.0"
|
version = "1.3.1"
|
||||||
description = "Sniff out which async library your code is running under"
|
description = "Sniff out which async library your code is running under"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -716,11 +705,11 @@ python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "soupsieve"
|
name = "soupsieve"
|
||||||
version = "2.4.1"
|
version = "2.5"
|
||||||
description = "A modern CSS selector implementation for Beautiful Soup."
|
description = "A modern CSS selector implementation for Beautiful Soup."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sphinx"
|
name = "sphinx"
|
||||||
|
@ -736,7 +725,6 @@ babel = ">=1.3"
|
||||||
colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
|
colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
|
||||||
docutils = ">=0.14,<0.18"
|
docutils = ">=0.14,<0.18"
|
||||||
imagesize = "*"
|
imagesize = "*"
|
||||||
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
|
|
||||||
Jinja2 = ">=2.3"
|
Jinja2 = ">=2.3"
|
||||||
packaging = "*"
|
packaging = "*"
|
||||||
Pygments = ">=2.0"
|
Pygments = ">=2.0"
|
||||||
|
@ -771,7 +759,7 @@ type_comments = ["typed-ast (>=1.4.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sphinx-material"
|
name = "sphinx-material"
|
||||||
version = "0.0.35"
|
version = "0.0.36"
|
||||||
description = "Material sphinx theme"
|
description = "Material sphinx theme"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -785,42 +773,45 @@ python-slugify = {version = "*", extras = ["unidecode"]}
|
||||||
sphinx = ">=2.0"
|
sphinx = ">=2.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["black (==19.10b0)"]
|
dev = ["black (==22.12.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sphinxcontrib-applehelp"
|
name = "sphinxcontrib-applehelp"
|
||||||
version = "1.0.4"
|
version = "1.0.8"
|
||||||
description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
|
description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.9"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
lint = ["flake8", "mypy", "docutils-stubs"]
|
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||||
|
standalone = ["Sphinx (>=5)"]
|
||||||
test = ["pytest"]
|
test = ["pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sphinxcontrib-devhelp"
|
name = "sphinxcontrib-devhelp"
|
||||||
version = "1.0.2"
|
version = "1.0.6"
|
||||||
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
|
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.9"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
lint = ["flake8", "mypy", "docutils-stubs"]
|
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||||
|
standalone = ["Sphinx (>=5)"]
|
||||||
test = ["pytest"]
|
test = ["pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sphinxcontrib-htmlhelp"
|
name = "sphinxcontrib-htmlhelp"
|
||||||
version = "2.0.1"
|
version = "2.0.5"
|
||||||
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
|
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.9"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
lint = ["flake8", "mypy", "docutils-stubs"]
|
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||||
|
standalone = ["Sphinx (>=5)"]
|
||||||
test = ["pytest", "html5lib"]
|
test = ["pytest", "html5lib"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -848,31 +839,33 @@ six = ">=1.5.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sphinxcontrib-qthelp"
|
name = "sphinxcontrib-qthelp"
|
||||||
version = "1.0.3"
|
version = "1.0.7"
|
||||||
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
|
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.9"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
lint = ["flake8", "mypy", "docutils-stubs"]
|
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||||
|
standalone = ["Sphinx (>=5)"]
|
||||||
test = ["pytest"]
|
test = ["pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sphinxcontrib-serializinghtml"
|
name = "sphinxcontrib-serializinghtml"
|
||||||
version = "1.1.5"
|
version = "1.1.10"
|
||||||
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
|
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.9"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
lint = ["flake8", "mypy", "docutils-stubs"]
|
lint = ["flake8", "mypy", "docutils-stubs"]
|
||||||
|
standalone = ["Sphinx (>=5)"]
|
||||||
test = ["pytest"]
|
test = ["pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "starlette"
|
name = "starlette"
|
||||||
version = "0.20.4"
|
version = "0.25.0"
|
||||||
description = "The little ASGI library that shines."
|
description = "The little ASGI library that shines."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -880,10 +873,9 @@ python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
anyio = ">=3.4.0,<5"
|
anyio = ">=3.4.0,<5"
|
||||||
typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
|
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "text-unidecode"
|
name = "text-unidecode"
|
||||||
|
@ -911,7 +903,7 @@ python-versions = ">=3.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomlkit"
|
name = "tomlkit"
|
||||||
version = "0.11.8"
|
version = "0.12.4"
|
||||||
description = "Style preserving TOML library"
|
description = "Style preserving TOML library"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -938,7 +930,7 @@ asyncmy = ["asyncmy (>=0.2.5,<0.3.0)"]
|
||||||
asyncodbc = ["asyncodbc (>=0.1.1,<0.2.0)"]
|
asyncodbc = ["asyncodbc (>=0.1.1,<0.2.0)"]
|
||||||
asyncpg = ["asyncpg"]
|
asyncpg = ["asyncpg"]
|
||||||
accel = ["ciso8601", "orjson", "uvloop"]
|
accel = ["ciso8601", "orjson", "uvloop"]
|
||||||
psycopg = ["psycopg[pool,binary] (==3.0.12)"]
|
psycopg = ["psycopg[binary,pool] (==3.0.12)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tweepy"
|
name = "tweepy"
|
||||||
|
@ -960,17 +952,25 @@ docs = ["myst-parser (==0.15.2)", "readthedocs-sphinx-search (==0.1.1)", "sphinx
|
||||||
socks = ["requests[socks] (>=2.27.0,<3)"]
|
socks = ["requests[socks] (>=2.27.0,<3)"]
|
||||||
test = ["vcrpy (>=1.10.3)"]
|
test = ["vcrpy (>=1.10.3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-toml"
|
||||||
|
version = "0.10.8.7"
|
||||||
|
description = "Typing stubs for toml"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.5.0"
|
version = "4.10.0"
|
||||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unidecode"
|
name = "unidecode"
|
||||||
version = "1.3.6"
|
version = "1.3.8"
|
||||||
description = "ASCII transliterations of Unicode text"
|
description = "ASCII transliterations of Unicode text"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -978,49 +978,37 @@ python-versions = ">=3.5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "1.26.15"
|
version = "1.26.18"
|
||||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
|
brotli = ["brotlicffi (>=0.8.0)", "brotli (==1.0.9)", "brotlipy (>=0.6.0)", "brotli (>=1.0.9)"]
|
||||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"]
|
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"]
|
||||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uvicorn"
|
name = "uvicorn"
|
||||||
version = "0.17.6"
|
version = "0.23.2"
|
||||||
description = "The lightning-fast ASGI server."
|
description = "The lightning-fast ASGI server."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
asgiref = ">=3.4.0"
|
|
||||||
click = ">=7.0"
|
click = ">=7.0"
|
||||||
h11 = ">=0.8"
|
h11 = ">=0.8"
|
||||||
|
typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchgod (>=0.6)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
|
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zipp"
|
|
||||||
version = "3.15.0"
|
|
||||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"]
|
|
||||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "jaraco.functools", "more-itertools", "big-o", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"]
|
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.10"
|
||||||
content-hash = "bfc1512cd6f94fdc013dbebcf70c0077093b2bc3126c8573c35a3569445f948d"
|
content-hash = "a81b518a3185eb0b2c42e6c927dab3f46dd91eb749cb52a1fbd0a462d51b685c"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
aerich = []
|
aerich = []
|
||||||
|
@ -1029,7 +1017,7 @@ alabaster = []
|
||||||
anyio = []
|
anyio = []
|
||||||
appdirs = []
|
appdirs = []
|
||||||
arrow = []
|
arrow = []
|
||||||
asgiref = []
|
async-timeout = []
|
||||||
asyncpg = []
|
asyncpg = []
|
||||||
asynctest = []
|
asynctest = []
|
||||||
atomicwrites = []
|
atomicwrites = []
|
||||||
|
@ -1045,6 +1033,7 @@ css-html-js-minify = []
|
||||||
dictdiffer = []
|
dictdiffer = []
|
||||||
docutils = []
|
docutils = []
|
||||||
dynaconf = []
|
dynaconf = []
|
||||||
|
exceptiongroup = []
|
||||||
facebook-sdk = []
|
facebook-sdk = []
|
||||||
fastapi = []
|
fastapi = []
|
||||||
fastapi-pagination = []
|
fastapi-pagination = []
|
||||||
|
@ -1053,7 +1042,6 @@ httpcore = []
|
||||||
httpx = []
|
httpx = []
|
||||||
idna = []
|
idna = []
|
||||||
imagesize = []
|
imagesize = []
|
||||||
importlib-metadata = []
|
|
||||||
iniconfig = []
|
iniconfig = []
|
||||||
iso8601 = []
|
iso8601 = []
|
||||||
jinja2 = []
|
jinja2 = []
|
||||||
|
@ -1078,7 +1066,6 @@ pytz = []
|
||||||
requests = []
|
requests = []
|
||||||
requests-oauthlib = []
|
requests-oauthlib = []
|
||||||
responses = []
|
responses = []
|
||||||
rfc3986 = []
|
|
||||||
six = []
|
six = []
|
||||||
sniffio = []
|
sniffio = []
|
||||||
snowballstemmer = []
|
snowballstemmer = []
|
||||||
|
@ -1100,8 +1087,8 @@ tomli = []
|
||||||
tomlkit = []
|
tomlkit = []
|
||||||
tortoise-orm = []
|
tortoise-orm = []
|
||||||
tweepy = []
|
tweepy = []
|
||||||
|
types-toml = []
|
||||||
typing-extensions = []
|
typing-extensions = []
|
||||||
unidecode = []
|
unidecode = []
|
||||||
urllib3 = []
|
urllib3 = []
|
||||||
uvicorn = []
|
uvicorn = []
|
||||||
zipp = []
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "mobilizon-reshare"
|
name = "mobilizon-reshare"
|
||||||
version = "0.3.2"
|
version = "0.3.6"
|
||||||
description = "A suite to reshare Mobilizon events on a broad selection of platforms"
|
description = "A suite to reshare Mobilizon events on a broad selection of platforms"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare"
|
homepage = "https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare"
|
||||||
|
@ -9,7 +9,7 @@ authors = ["Simone Robutti <simone.robutti@protonmail.com>"]
|
||||||
license = "Coopyleft"
|
license = "Coopyleft"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = "^3.10"
|
||||||
dynaconf = "~3.1"
|
dynaconf = "~3.1"
|
||||||
tortoise-orm = {extras = ["asyncpg"], version = "~0.19"}
|
tortoise-orm = {extras = ["asyncpg"], version = "~0.19"}
|
||||||
aiosqlite = "~0.17"
|
aiosqlite = "~0.17"
|
||||||
|
@ -18,17 +18,17 @@ requests = "~2.28"
|
||||||
arrow = "~1.1"
|
arrow = "~1.1"
|
||||||
click = "~8.1"
|
click = "~8.1"
|
||||||
beautifulsoup4 = "~4.11"
|
beautifulsoup4 = "~4.11"
|
||||||
markdownify = "~0.10"
|
markdownify = "~0.11"
|
||||||
appdirs = "~1.4"
|
appdirs = "~1.4"
|
||||||
tweepy = "~4.13"
|
tweepy = "~4.13"
|
||||||
facebook-sdk = "~3.1"
|
facebook-sdk = "~3.1"
|
||||||
aerich = "~0.6"
|
aerich = "~0.6"
|
||||||
fastapi = "~0.85"
|
fastapi = "~0.92"
|
||||||
uvicorn = "~0.17"
|
uvicorn = "~0.23"
|
||||||
fastapi-pagination = "^0.11.0"
|
fastapi-pagination = "~0.12"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
responses = "~0.13"
|
responses = "~0.22"
|
||||||
pytest-asyncio = "~0.15"
|
pytest-asyncio = "~0.15"
|
||||||
asynctest = "~0.13"
|
asynctest = "~0.13"
|
||||||
pytest = "~6.2"
|
pytest = "~6.2"
|
||||||
|
@ -38,7 +38,7 @@ Sphinx = "~4.4"
|
||||||
sphinxcontrib-napoleon = "~0.7"
|
sphinxcontrib-napoleon = "~0.7"
|
||||||
sphinx-material = "~0.0"
|
sphinx-material = "~0.0"
|
||||||
sphinx-autodoc-typehints = "~1.17"
|
sphinx-autodoc-typehints = "~1.17"
|
||||||
httpx = "~0.23"
|
httpx = "~0.24"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
debug = false
|
debug = false
|
||||||
default = true
|
default = true
|
||||||
local_state_dir = "/var/lib/mobilizon-reshare"
|
local_state_dir = "/var/lib/mobilizon-reshare"
|
||||||
#db_path = "@format {this.local_state_dir}/events.db"
|
#db_url = "@format sqlite://{this.local_state_dir}/events.db"
|
||||||
db_url = "@format postgres://mobilizon_reshare:mobilizon_reshare@db:5432/mobilizon_reshare"
|
db_url = "@format postgres://mobilizon_reshare:mobilizon_reshare@db:5432/mobilizon_reshare"
|
||||||
|
locale = "en-uk"
|
||||||
|
|
||||||
[default.source.mobilizon]
|
[default.source.mobilizon]
|
||||||
url="https://some-mobilizon.com/api"
|
url="https://some-mobilizon.com/api"
|
||||||
|
@ -28,6 +29,15 @@ class = "logging.StreamHandler"
|
||||||
formatter = "standard"
|
formatter = "standard"
|
||||||
stream = "ext://sys.stderr"
|
stream = "ext://sys.stderr"
|
||||||
|
|
||||||
|
[default.logging.handlers.file]
|
||||||
|
level = "INFO"
|
||||||
|
class = "logging.handlers.RotatingFileHandler"
|
||||||
|
formatter = "standard"
|
||||||
|
filename = "@format {this.local_state_dir}/mobilizon_reshare.log"
|
||||||
|
maxBytes = 52428800
|
||||||
|
backupCount = 500
|
||||||
|
encoding = "utf8"
|
||||||
|
|
||||||
[default.logging.root]
|
[default.logging.root]
|
||||||
level = "DEBUG"
|
level = "INFO"
|
||||||
handlers = ['console']
|
handlers = ['console', 'file']
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -e
|
||||||
|
|
||||||
guix time-machine -C channels-lock.scm -- build -f guix.scm
|
if [ "$1" = "--release" ] || [ "$1" = "-r" ]; then
|
||||||
|
with_input="--with-input=mobilizon-reshare.git=mobilizon-reshare"
|
||||||
|
fi
|
||||||
|
|
||||||
guix time-machine -C channels-lock.scm -- pack -L . -f docker -S /opt/bin=bin --save-provenance --root=docker-image.tar.gz --entry-point=bin/scheduler.py mobilizon-reshare-scheduler
|
guix time-machine -C channels-lock.scm -- build -L . ${with_input} mobilizon-reshare-scheduler
|
||||||
|
|
||||||
|
guix time-machine -C channels-lock.scm -- pack -L . ${with_input} -f docker -S /opt/bin=bin --save-provenance --root=docker-image.tar.gz --entry-point=bin/scheduler.py mobilizon-reshare-scheduler python
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
project_root="$(cd "$(dirname $(dirname "$0"))" && pwd)"
|
||||||
|
|
||||||
|
get_version () {
|
||||||
|
cat "${project_root}/mobilizon_reshare/VERSION"
|
||||||
|
}
|
||||||
|
|
||||||
|
python -m pybadges \
|
||||||
|
--left-text="python" \
|
||||||
|
--right-text="3.10, 3.11" \
|
||||||
|
--whole-link="https://www.python.org/" \
|
||||||
|
--browser \
|
||||||
|
--logo='https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/python.svg' \
|
||||||
|
--embed-logo=yes
|
||||||
|
|
||||||
|
python -m pybadges \
|
||||||
|
--left-text="pypi" \
|
||||||
|
--right-text="$(get_version)" \
|
||||||
|
--whole-link="https://pypi.org/project/mobilizon-reshare/" \
|
||||||
|
--browser
|
||||||
|
|
||||||
|
python -m pybadges \
|
||||||
|
--left-text="LICENSE" \
|
||||||
|
--right-text="Coopyleft" \
|
||||||
|
--whole-link="https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare/blob/master/LICENSE" \
|
||||||
|
--browser
|
|
@ -5,7 +5,7 @@ set -e
|
||||||
export MOBILIZON_RESHARE_LOG_DIR="/tmp"
|
export MOBILIZON_RESHARE_LOG_DIR="/tmp"
|
||||||
export MOBILIZON_RESHARE_LOCAL_STATE_DIR="/tmp"
|
export MOBILIZON_RESHARE_LOCAL_STATE_DIR="/tmp"
|
||||||
export SECRETS_FOR_DYNACONF="$(pwd)/.secrets.toml"
|
export SECRETS_FOR_DYNACONF="$(pwd)/.secrets.toml"
|
||||||
export SETTINGS_FILE_FOR_DYNACONF="$(pwd)/settings.toml"
|
export SETTINGS_FILE_FOR_DYNACONF="$(pwd)/mobilizon_reshare/settings.toml"
|
||||||
export ENV_FOR_DYNACONF="production"
|
export ENV_FOR_DYNACONF="production"
|
||||||
|
|
||||||
poetry run mobilizon-reshare "$@"
|
poetry run mobilizon-reshare "$@"
|
||||||
|
|
|
@ -14,16 +14,27 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
from apscheduler.triggers.cron import CronTrigger
|
from apscheduler.triggers.cron import CronTrigger
|
||||||
|
|
||||||
from mobilizon_reshare.cli import _safe_execution
|
from mobilizon_reshare.cli import _safe_execution
|
||||||
from mobilizon_reshare.cli.commands.recap.main import recap
|
from mobilizon_reshare.cli.commands.recap.main import recap as recap_main
|
||||||
from mobilizon_reshare.cli.commands.start.main import start
|
from mobilizon_reshare.cli.commands.start.main import start as start_main
|
||||||
|
from mobilizon_reshare.config.command import CommandConfig
|
||||||
|
|
||||||
sched = AsyncIOScheduler()
|
sched = AsyncIOScheduler()
|
||||||
|
config = CommandConfig(dry_run=False)
|
||||||
|
|
||||||
|
|
||||||
|
async def start():
|
||||||
|
await start_main(config)
|
||||||
|
|
||||||
|
|
||||||
|
async def recap():
|
||||||
|
await recap_main(config)
|
||||||
|
|
||||||
|
|
||||||
# Runs "start" from Monday to Friday every 15 mins
|
# Runs "start" from Monday to Friday every 15 mins
|
||||||
sched.add_job(
|
sched.add_job(
|
||||||
partial(_safe_execution, start),
|
partial(_safe_execution, start),
|
||||||
CronTrigger.from_crontab(
|
CronTrigger.from_crontab(
|
||||||
os.environ.get("MOBILIZON_RESHARE_INTERVAL", "*/15 10-18 * * 0-4")
|
os.environ.get("MOBILIZON_RESHARE_INTERVAL", "*/15 10-18 * * 1-4")
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
# Runs "recap" once a week
|
# Runs "recap" once a week
|
||||||
|
|
|
@ -8,7 +8,6 @@ import mobilizon_reshare.publishers
|
||||||
import mobilizon_reshare.storage.query.read
|
import mobilizon_reshare.storage.query.read
|
||||||
from mobilizon_reshare.models.publisher import Publisher
|
from mobilizon_reshare.models.publisher import Publisher
|
||||||
import mobilizon_reshare.main.recap
|
import mobilizon_reshare.main.recap
|
||||||
from mobilizon_reshare.publishers.coordinators.event_publishing import notify
|
|
||||||
from tests import today
|
from tests import today
|
||||||
from tests.conftest import event_1, event_0
|
from tests.conftest import event_1, event_0
|
||||||
|
|
||||||
|
@ -138,15 +137,41 @@ async def mock_notifier_config(monkeypatch, publisher_class, mock_formatter_clas
|
||||||
return mock_formatter_class
|
return mock_formatter_class
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
notify, "get_notifier_class", _mock_notifier_class,
|
mobilizon_reshare.publishers.coordinators.event_publishing.notify,
|
||||||
|
"get_notifier_class",
|
||||||
|
_mock_notifier_class,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
mobilizon_reshare.publishers.coordinators.event_publishing.notify,
|
||||||
|
"get_formatter_class",
|
||||||
|
_mock_format_class,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
mobilizon_reshare.publishers.coordinators.event_publishing.notify,
|
||||||
|
"get_notifier_class",
|
||||||
|
_mock_notifier_class,
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
mobilizon_reshare.publishers.platforms.platform_mapping,
|
mobilizon_reshare.publishers.platforms.platform_mapping,
|
||||||
"get_formatter_class",
|
"get_formatter_class",
|
||||||
_mock_format_class,
|
_mock_format_class,
|
||||||
)
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
mobilizon_reshare.publishers.coordinators.event_publishing.notify,
|
||||||
|
"get_formatter_class",
|
||||||
|
_mock_format_class,
|
||||||
|
)
|
||||||
|
|
||||||
monkeypatch.setattr(notify, "get_active_notifiers", _mock_active_notifier)
|
monkeypatch.setattr(
|
||||||
|
mobilizon_reshare.publishers.coordinators.event_publishing.notify,
|
||||||
|
"get_active_notifiers",
|
||||||
|
_mock_active_notifier,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
mobilizon_reshare.config.notifiers,
|
||||||
|
"get_active_notifiers",
|
||||||
|
lambda s: [],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
from logging import DEBUG
|
from logging import DEBUG
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from mobilizon_reshare.dataclasses import EventPublicationStatus
|
from mobilizon_reshare.dataclasses import EventPublicationStatus
|
||||||
from mobilizon_reshare.dataclasses import MobilizonEvent
|
from mobilizon_reshare.dataclasses import MobilizonEvent
|
||||||
from mobilizon_reshare.main.publish import select_and_publish, publish_event
|
from mobilizon_reshare.main.publish import select_and_publish, publish_by_mobilizon_id
|
||||||
|
from mobilizon_reshare.models.notification import NotificationStatus, Notification
|
||||||
from mobilizon_reshare.models.event import Event
|
from mobilizon_reshare.models.event import Event
|
||||||
from mobilizon_reshare.models.publication import PublicationStatus
|
from mobilizon_reshare.models.publication import PublicationStatus
|
||||||
from mobilizon_reshare.storage.query.read import get_all_publications
|
from mobilizon_reshare.storage.query.read import get_all_publications, get_event
|
||||||
from tests.conftest import event_0, event_1
|
from tests.conftest import event_0, event_1
|
||||||
|
|
||||||
one_unpublished_event_specification = {
|
one_unpublished_event_specification = {
|
||||||
|
@ -102,7 +104,9 @@ async def test_publish_event(
|
||||||
await generate_models(one_unpublished_event_specification)
|
await generate_models(one_unpublished_event_specification)
|
||||||
with caplog.at_level(DEBUG):
|
with caplog.at_level(DEBUG):
|
||||||
# calling mobilizon-reshare publish -E <UUID> -p <platform>
|
# calling mobilizon-reshare publish -E <UUID> -p <platform>
|
||||||
report = await publish_event(event_0, command_config, publishers)
|
report = await publish_by_mobilizon_id(
|
||||||
|
event_0.mobilizon_id, command_config, publishers
|
||||||
|
)
|
||||||
assert report is not None
|
assert report is not None
|
||||||
assert report.successful
|
assert report.successful
|
||||||
|
|
||||||
|
@ -112,3 +116,50 @@ async def test_publish_event(
|
||||||
assert len(publications) == len(expected)
|
assert len(publications) == len(expected)
|
||||||
assert all(p.status == PublicationStatus.COMPLETED for p in publications)
|
assert all(p.status == PublicationStatus.COMPLETED for p in publications)
|
||||||
assert {p.publisher.name for p in publications} == expected
|
assert {p.publisher.name for p in publications} == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"publisher_class", [pytest.lazy_fixture("mock_publisher_invalid_class")]
|
||||||
|
)
|
||||||
|
async def test_notify_publisher_failure(
|
||||||
|
caplog,
|
||||||
|
mock_publisher_config,
|
||||||
|
message_collector,
|
||||||
|
generate_models,
|
||||||
|
mock_notifier_config,
|
||||||
|
command_config,
|
||||||
|
):
|
||||||
|
await generate_models(one_unpublished_event_specification)
|
||||||
|
|
||||||
|
with caplog.at_level(DEBUG):
|
||||||
|
# calling the publish command
|
||||||
|
result = await select_and_publish(command_config)
|
||||||
|
|
||||||
|
assert not result.successful
|
||||||
|
assert len(result.reports) == 1
|
||||||
|
assert result.reports[0].published_content is None
|
||||||
|
|
||||||
|
# since the db contains at least one event, this has to be picked and published
|
||||||
|
event_model = await get_event(UUID(int=0))
|
||||||
|
# it should create a publication for each publisher and a notification for each notifier
|
||||||
|
publications = event_model.publications
|
||||||
|
assert len(publications) == 1, publications
|
||||||
|
publication = publications[0]
|
||||||
|
notifications: list[Notification] = list(publications[0].notifications)
|
||||||
|
assert len(notifications) == 2, notifications
|
||||||
|
|
||||||
|
# all the publications for event should be saved as FAILED
|
||||||
|
for n in notifications:
|
||||||
|
assert n.status == NotificationStatus.COMPLETED
|
||||||
|
assert (
|
||||||
|
n.message
|
||||||
|
== f"Publication {publication.id} failed with status: FAILED.\nReason: credentials error"
|
||||||
|
"\nPublisher: mock\nEvent: event_0"
|
||||||
|
)
|
||||||
|
|
||||||
|
# the derived status for the event should be FAILED
|
||||||
|
assert (
|
||||||
|
MobilizonEvent.from_model(event_model).status
|
||||||
|
== EventPublicationStatus.FAILED
|
||||||
|
)
|
||||||
|
|
|
@ -122,16 +122,16 @@ async def test_retry_publication_missing(
|
||||||
async def test_event_retry_failure(
|
async def test_event_retry_failure(
|
||||||
event_with_failed_publication,
|
event_with_failed_publication,
|
||||||
mock_publisher_config,
|
mock_publisher_config,
|
||||||
|
mock_notifier_config,
|
||||||
failed_publication: Publication,
|
failed_publication: Publication,
|
||||||
caplog,
|
|
||||||
):
|
):
|
||||||
|
|
||||||
with caplog.at_level(ERROR):
|
report = await retry_event(event_with_failed_publication.mobilizon_id)
|
||||||
await retry_event(event_with_failed_publication.mobilizon_id)
|
assert len(report.reports) == 1
|
||||||
assert (
|
assert (
|
||||||
f"Publication {failed_publication.id} failed with status: 0.\nReason: credentials error"
|
f"Publication {failed_publication.id} failed with status: FAILED.\nReason: credentials error"
|
||||||
in caplog.text
|
in report.reports[0].get_failure_message()
|
||||||
)
|
)
|
||||||
|
|
||||||
p = await Publication.filter(id=failed_publication.id).first()
|
p = await Publication.filter(id=failed_publication.id).first()
|
||||||
assert p.status == PublicationStatus.FAILED, p.id
|
assert p.status == PublicationStatus.FAILED, p.id
|
||||||
|
@ -144,15 +144,17 @@ async def test_event_retry_failure(
|
||||||
async def test_publication_retry_failure(
|
async def test_publication_retry_failure(
|
||||||
event_with_failed_publication,
|
event_with_failed_publication,
|
||||||
mock_publisher_config,
|
mock_publisher_config,
|
||||||
|
mock_notifier_config,
|
||||||
failed_publication: Publication,
|
failed_publication: Publication,
|
||||||
caplog,
|
caplog,
|
||||||
):
|
):
|
||||||
|
|
||||||
with caplog.at_level(ERROR):
|
with caplog.at_level(ERROR):
|
||||||
await retry_publication(failed_publication.id)
|
report = await retry_publication(failed_publication.id)
|
||||||
|
assert len(report.reports) == 1
|
||||||
assert (
|
assert (
|
||||||
f"Publication {failed_publication.id} failed with status: 0.\nReason: credentials error"
|
f"Publication {failed_publication.id} failed with status: FAILED.\nReason: credentials error"
|
||||||
in caplog.text
|
in report.reports[0].get_failure_message()
|
||||||
)
|
)
|
||||||
p = await Publication.filter(id=failed_publication.id).first()
|
p = await Publication.filter(id=failed_publication.id).first()
|
||||||
assert p.status == PublicationStatus.FAILED, p.id
|
assert p.status == PublicationStatus.FAILED, p.id
|
||||||
|
|
|
@ -186,7 +186,7 @@ async def test_start_publisher_failure(
|
||||||
|
|
||||||
assert "Event to publish found" in caplog.text
|
assert "Event to publish found" in caplog.text
|
||||||
assert message_collector == [
|
assert message_collector == [
|
||||||
f"Publication {p.id} failed with status: 0."
|
f"Publication {p.id} failed with status: FAILED."
|
||||||
f"\nReason: credentials error\nPublisher: mock\nEvent: test event"
|
f"\nReason: credentials error\nPublisher: mock\nEvent: test event"
|
||||||
for p in publications
|
for p in publications
|
||||||
for _ in range(2)
|
for _ in range(2)
|
||||||
|
|
|
@ -51,7 +51,7 @@ def generate_event_status(published):
|
||||||
|
|
||||||
|
|
||||||
def generate_notification_status(published):
|
def generate_notification_status(published):
|
||||||
return NotificationStatus.COMPLETED if published else NotificationStatus.WAITING
|
return NotificationStatus.COMPLETED if published else NotificationStatus.FAILED
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
@ -421,6 +421,12 @@ def mock_publisher_valid(message_collector, mock_publisher_class):
|
||||||
return mock_publisher_class()
|
return mock_publisher_class()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_zulip_publisher(message_collector, mock_zulip_publisher_class):
|
||||||
|
|
||||||
|
return mock_zulip_publisher_class()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mobilizon_url():
|
def mobilizon_url():
|
||||||
return get_settings()["source"]["mobilizon"]["url"]
|
return get_settings()["source"]["mobilizon"]["url"]
|
||||||
|
|
|
@ -8,5 +8,5 @@ async def test_notification_create(notification_model_generator):
|
||||||
notification_model = notification_model_generator()
|
notification_model = notification_model_generator()
|
||||||
await notification_model.save()
|
await notification_model.save()
|
||||||
notification_db = await Notification.all().first()
|
notification_db = await Notification.all().first()
|
||||||
assert notification_db.status == NotificationStatus.WAITING
|
assert notification_db.status == NotificationStatus.FAILED
|
||||||
assert notification_db.message == "message_1"
|
assert notification_db.message == "message_1"
|
||||||
|
|
|
@ -78,3 +78,21 @@ def mock_publisher_invalid_response(message_collector):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return MockPublisher()
|
return MockPublisher()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_zulip_publisher_invalid_response(message_collector):
|
||||||
|
class MockPublisher(AbstractPlatform):
|
||||||
|
|
||||||
|
name = "zulip"
|
||||||
|
|
||||||
|
def _send(self, message, event):
|
||||||
|
message_collector.append(message)
|
||||||
|
|
||||||
|
def _validate_response(self, response):
|
||||||
|
raise InvalidResponse("Invalid response")
|
||||||
|
|
||||||
|
def validate_credentials(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return MockPublisher()
|
||||||
|
|
|
@ -115,8 +115,12 @@ async def mock_publications(
|
||||||
|
|
||||||
@pytest.mark.parametrize("num_publications", [2])
|
@pytest.mark.parametrize("num_publications", [2])
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_publication_coordinator_run_success(mock_publications,):
|
async def test_publication_coordinator_run_success(
|
||||||
coordinator = PublisherCoordinator(publications=mock_publications,)
|
mock_publications,
|
||||||
|
):
|
||||||
|
coordinator = PublisherCoordinator(
|
||||||
|
publications=mock_publications,
|
||||||
|
)
|
||||||
report = coordinator.run()
|
report = coordinator.run()
|
||||||
assert len(report.reports) == 2
|
assert len(report.reports) == 2
|
||||||
assert report.successful, "\n".join(map(lambda rep: rep.reason, report.reports))
|
assert report.successful, "\n".join(map(lambda rep: rep.reason, report.reports))
|
||||||
|
@ -173,12 +177,12 @@ async def test_publication_coordinator_run_failure_response(
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_notifier_coordinator_publication_failed(
|
async def test_notifier_coordinator_publication_failed(
|
||||||
mock_publisher_valid, failure_report
|
mock_zulip_publisher, failure_report
|
||||||
):
|
):
|
||||||
mock_send = MagicMock()
|
mock_send = MagicMock()
|
||||||
mock_publisher_valid._send = mock_send
|
mock_zulip_publisher._send = mock_send
|
||||||
coordinator = PublicationFailureNotifiersCoordinator(
|
coordinator = PublicationFailureNotifiersCoordinator(
|
||||||
failure_report, [mock_publisher_valid, mock_publisher_valid]
|
failure_report, [mock_zulip_publisher, mock_zulip_publisher]
|
||||||
)
|
)
|
||||||
coordinator.notify_failure()
|
coordinator.notify_failure()
|
||||||
|
|
||||||
|
@ -188,18 +192,18 @@ async def test_notifier_coordinator_publication_failed(
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_notifier_coordinator_error(
|
async def test_notifier_coordinator_error(
|
||||||
failure_report, mock_publisher_invalid_response, caplog
|
failure_report, mock_zulip_publisher_invalid_response, caplog
|
||||||
):
|
):
|
||||||
mock_send = MagicMock()
|
mock_send = MagicMock()
|
||||||
mock_publisher_invalid_response._send = mock_send
|
mock_zulip_publisher_invalid_response._send = mock_send
|
||||||
|
|
||||||
coordinator = PublicationFailureNotifiersCoordinator(
|
coordinator = PublicationFailureNotifiersCoordinator(
|
||||||
failure_report,
|
failure_report,
|
||||||
[mock_publisher_invalid_response, mock_publisher_invalid_response],
|
[mock_zulip_publisher_invalid_response, mock_zulip_publisher_invalid_response],
|
||||||
)
|
)
|
||||||
with caplog.at_level(logging.CRITICAL):
|
with caplog.at_level(logging.CRITICAL):
|
||||||
coordinator.notify_failure()
|
coordinator.notify_failure()
|
||||||
assert "Failed to send" in caplog.text
|
assert "Failed to notify failure of" in caplog.text
|
||||||
assert failure_report.get_failure_message() in caplog.text
|
assert failure_report.get_failure_message() in caplog.text
|
||||||
# 4 = 2 reports * 2 notifiers
|
# 4 = 2 reports * 2 notifiers
|
||||||
assert mock_send.call_count == 2
|
assert mock_send.call_count == 2
|
||||||
|
|
|
@ -9,10 +9,15 @@ from mobilizon_reshare.dataclasses.event import (
|
||||||
get_mobilizon_events_with_status,
|
get_mobilizon_events_with_status,
|
||||||
get_mobilizon_events_without_publications,
|
get_mobilizon_events_without_publications,
|
||||||
)
|
)
|
||||||
|
from mobilizon_reshare.storage.query.read import (
|
||||||
|
get_all_events,
|
||||||
|
get_event,
|
||||||
|
)
|
||||||
from mobilizon_reshare.dataclasses.publication import build_publications_for_event
|
from mobilizon_reshare.dataclasses.publication import build_publications_for_event
|
||||||
from mobilizon_reshare.models.publication import PublicationStatus
|
from mobilizon_reshare.models.publication import PublicationStatus
|
||||||
from mobilizon_reshare.storage.query.read import publications_with_status
|
from mobilizon_reshare.storage.query.read import publications_with_status
|
||||||
from tests import today
|
from tests import today
|
||||||
|
from tests.commands.test_publish import one_unpublished_event_specification
|
||||||
from tests.conftest import event_0, event_1, event_3
|
from tests.conftest import event_0, event_1, event_3
|
||||||
from tests.storage import complete_specification
|
from tests.storage import complete_specification
|
||||||
from tests.storage import result_publication
|
from tests.storage import result_publication
|
||||||
|
@ -153,6 +158,14 @@ async def test_events_without_publications(spec, expected_events, generate_model
|
||||||
assert unpublished_events == expected_events
|
assert unpublished_events == expected_events
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_all_events(generate_models):
|
||||||
|
await generate_models(one_unpublished_event_specification)
|
||||||
|
|
||||||
|
all_events = [await get_event(event_0.mobilizon_id)]
|
||||||
|
assert list(await get_all_events()) == all_events
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"mock_active_publishers, spec, event, n_publications",
|
"mock_active_publishers, spec, event, n_publications",
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from mobilizon_reshare.dataclasses.event import get_all_mobilizon_events
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_get_all_events(event_generator):
|
|
||||||
all_events = [
|
|
||||||
event_generator(mobilizon_id=UUID(int=i), published=False) for i in range(4)
|
|
||||||
]
|
|
||||||
for e in all_events:
|
|
||||||
await e.to_model().save()
|
|
||||||
|
|
||||||
assert list(await get_all_mobilizon_events()) == all_events
|
|
Loading…
Reference in New Issue