Compare commits
No commits in common. "95654e9e3e3f0001fb4c1082a2868b7e27bdc9dc" and "09c422efe207533d29b4f94acbf00eb649a05187" have entirely different histories.
95654e9e3e
...
09c422efe2
24 changed files with 177 additions and 296 deletions
|
@ -1,84 +0,0 @@
|
||||||
---
|
|
||||||
name: Build and publish Debian & Python packages
|
|
||||||
on: ["create"]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: docker
|
|
||||||
container:
|
|
||||||
image: docker.io/brenard/debian-python-deb:latest
|
|
||||||
steps:
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Build Debian & Python package
|
|
||||||
env:
|
|
||||||
MAINTAINER_NAME: ${{ vars.MAINTAINER_NAME }}
|
|
||||||
MAINTAINER_EMAIL: ${{ vars.MAINTAINER_EMAIL }}
|
|
||||||
DEBIAN_CODENAME: ${{ vars.DEBIAN_CODENAME }}
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.GPG_KEY }}"|base64 -d|gpg --import
|
|
||||||
./build.sh
|
|
||||||
rm -fr deb_dist/mylib-*
|
|
||||||
- name: Upload Debian & Python package files
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: |
|
|
||||||
dist
|
|
||||||
deb_dist
|
|
||||||
|
|
||||||
publish-forgejo:
|
|
||||||
runs-on: docker
|
|
||||||
container:
|
|
||||||
image: docker.io/brenard/debian-python-deb:latest
|
|
||||||
steps:
|
|
||||||
- name: Download Debian & Python packages files
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
|
|
||||||
- name: Create the release
|
|
||||||
id: create-release
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
mkdir release
|
|
||||||
mv dist/*.whl release/
|
|
||||||
mv deb_dist/*.deb release/
|
|
||||||
md5sum release/* > md5sum.txt
|
|
||||||
sha512sum release/* > sha512sum.txt
|
|
||||||
mv md5sum.txt sha512sum.txt release/
|
|
||||||
{
|
|
||||||
echo 'release_note<<EOF'
|
|
||||||
cat dist/release_notes.md
|
|
||||||
echo 'EOF'
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Publish release on Forgejo
|
|
||||||
uses: actions/forgejo-release@v1
|
|
||||||
with:
|
|
||||||
direction: upload
|
|
||||||
url: https://gitea.zionetrix.net
|
|
||||||
token: ${{ secrets.forgejo_token }}
|
|
||||||
release-dir: release
|
|
||||||
release-notes: ${{ steps.create-release.outputs.release_note }}
|
|
||||||
|
|
||||||
publish-aptly:
|
|
||||||
runs-on: docker
|
|
||||||
container:
|
|
||||||
image: docker.io/brenard/aptly-publish:latest
|
|
||||||
steps:
|
|
||||||
- name: "Download Debian package files"
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
|
|
||||||
- name: "Publish Debian package on Aptly repository"
|
|
||||||
uses: https://gitea.zionetrix.net/bn8/aptly-publish@master
|
|
||||||
with:
|
|
||||||
api_url: ${{ vars.apt_api_url }}
|
|
||||||
api_username: ${{ vars.apt_api_username }}
|
|
||||||
api_password: ${{ secrets.apt_api_password }}
|
|
||||||
repo_name: ${{ vars.apt_repo_name }}
|
|
||||||
path: "deb_dist"
|
|
||||||
source_name: ${{ vars.apt_source_name }}
|
|
|
@ -1,30 +0,0 @@
|
||||||
---
|
|
||||||
name: Run tests
|
|
||||||
on: [push]
|
|
||||||
jobs:
|
|
||||||
tests:
|
|
||||||
runs-on: docker
|
|
||||||
container:
|
|
||||||
image: docker.io/brenard/python-pre-commit:latest
|
|
||||||
steps:
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Install dependencies
|
|
||||||
env:
|
|
||||||
DEBIAN_FRONTEND: noninteractive
|
|
||||||
run: |
|
|
||||||
apt-get -qq update
|
|
||||||
apt-get -qq -y install --no-install-recommends \
|
|
||||||
build-essential \
|
|
||||||
python3 \
|
|
||||||
python3-dev \
|
|
||||||
libldap2-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
pkg-config \
|
|
||||||
libsystemd-dev \
|
|
||||||
libpq-dev \
|
|
||||||
libmariadb-dev
|
|
||||||
- name: Run tests.sh
|
|
||||||
run: ./tests.sh
|
|
|
@ -1,58 +1,31 @@
|
||||||
# Pre-commit hooks to run tests and ensure code is cleaned.
|
# Pre-commit hooks to run tests and ensure code is cleaned.
|
||||||
# See https://pre-commit.com for more information
|
# See https://pre-commit.com for more information
|
||||||
---
|
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
||||||
rev: v0.1.6
|
|
||||||
hooks:
|
|
||||||
- id: ruff
|
|
||||||
args: ["--fix"]
|
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.15.0
|
rev: v3.3.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: ["--keep-percent-format", "--py37-plus"]
|
args: ['--keep-percent-format', '--py37-plus']
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 23.11.0
|
rev: 22.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: ["--target-version", "py37", "--line-length", "100"]
|
args: ['--target-version', 'py37', '--line-length', '100']
|
||||||
- repo: https://github.com/PyCQA/isort
|
- repo: https://github.com/PyCQA/isort
|
||||||
rev: 5.12.0
|
rev: 5.11.5
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
args: ["--profile", "black", "--line-length", "100"]
|
args: ['--profile', 'black', '--line-length', '100']
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 6.1.0
|
rev: 6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
args: ["--max-line-length=100"]
|
args: ['--max-line-length=100']
|
||||||
- repo: https://github.com/codespell-project/codespell
|
|
||||||
rev: v2.2.2
|
|
||||||
hooks:
|
|
||||||
- id: codespell
|
|
||||||
args:
|
|
||||||
- --ignore-words-list=exten
|
|
||||||
- --skip="./.*,*.csv,*.json,*.ini,*.subject,*.txt,*.html,*.log,*.conf"
|
|
||||||
- --quiet-level=2
|
|
||||||
- --ignore-regex=.*codespell-ignore$
|
|
||||||
# - --write-changes # Uncomment to write changes
|
|
||||||
exclude_types: [csv, json]
|
|
||||||
- repo: https://github.com/adrienverge/yamllint
|
|
||||||
rev: v1.32.0
|
|
||||||
hooks:
|
|
||||||
- id: yamllint
|
|
||||||
ignore: .github/
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
|
||||||
rev: v2.7.1
|
|
||||||
hooks:
|
|
||||||
- id: prettier
|
|
||||||
args: ["--print-width", "100"]
|
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: pylint
|
- id: pylint
|
||||||
name: pylint
|
name: pylint
|
||||||
entry: ./.pre-commit-pylint --extension-pkg-whitelist=cx_Oracle
|
entry: pylint --extension-pkg-whitelist=cx_Oracle
|
||||||
language: system
|
language: system
|
||||||
types: [python]
|
types: [python]
|
||||||
require_serial: true
|
require_serial: true
|
||||||
|
@ -65,7 +38,7 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: pytest
|
- id: pytest
|
||||||
name: pytest
|
name: pytest
|
||||||
entry: ./.pre-commit-pytest tests
|
entry: python3 -m pytest tests
|
||||||
language: system
|
language: system
|
||||||
types: [python]
|
types: [python]
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
PWD=`pwd`
|
|
||||||
|
|
||||||
if [ -d "$PWD/venv" ]
|
|
||||||
then
|
|
||||||
echo "Run pylint inside venv ($PWD/venv)..."
|
|
||||||
[ ! -e "$PWD/venv/bin/pylint" ] && $PWD/venv/bin/python -m pip install pylint
|
|
||||||
$PWD/venv/bin/pylint "$@"
|
|
||||||
exit $?
|
|
||||||
elif [ -e "$PWD/pyproject.toml" ]
|
|
||||||
then
|
|
||||||
echo "Run pylint using poetry..."
|
|
||||||
poetry run pylint --version > /dev/null 2>&1 || poetry run python -m pip install pylint
|
|
||||||
poetry run pylint "$@"
|
|
||||||
exit $?
|
|
||||||
else
|
|
||||||
echo "Run pylint at system scope..."
|
|
||||||
pylint "$@"
|
|
||||||
exit $?
|
|
||||||
fi
|
|
|
@ -1,21 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
PWD=`pwd`
|
|
||||||
|
|
||||||
if [ -d "$PWD/venv" ]
|
|
||||||
then
|
|
||||||
echo "Run pytest inside venv ($PWD/venv)..."
|
|
||||||
[ ! -e "$PWD/venv/bin/pytest" ] && $PWD/venv/bin/python -m pip install pytest
|
|
||||||
$PWD/venv/bin/pytest "$@"
|
|
||||||
exit $?
|
|
||||||
elif [ -e "$PWD/pyproject.toml" ]
|
|
||||||
then
|
|
||||||
echo "Run pytest using poetry..."
|
|
||||||
poetry run pytest --version > /dev/null 2>&1 || poetry run python -m pip install pytest
|
|
||||||
poetry run pytest "$@"
|
|
||||||
exit $?
|
|
||||||
else
|
|
||||||
echo "Run pytest at system scope..."
|
|
||||||
pytest "$@"
|
|
||||||
exit $?
|
|
||||||
fi
|
|
63
.woodpecker.yml
Normal file
63
.woodpecker.yml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
clone:
|
||||||
|
git:
|
||||||
|
image: woodpeckerci/plugin-git
|
||||||
|
tags: true
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
test:
|
||||||
|
image: brenard/mylib:dev-master
|
||||||
|
commands:
|
||||||
|
- ./tests.sh --no-venv
|
||||||
|
|
||||||
|
build:
|
||||||
|
image: brenard/debian-python-deb
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
commands:
|
||||||
|
- echo "$GPG_KEY"|base64 -d|gpg --import
|
||||||
|
- ./build.sh --quiet
|
||||||
|
- rm -fr deb_dist/mylib-*
|
||||||
|
secrets: [ maintainer_name, maintainer_email, gpg_key, debian_codename ]
|
||||||
|
|
||||||
|
publish-dryrun:
|
||||||
|
group: publish
|
||||||
|
image: alpine
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
commands:
|
||||||
|
- ls dist/*
|
||||||
|
- ls deb_dist/*
|
||||||
|
|
||||||
|
publish-gitea:
|
||||||
|
group: publish
|
||||||
|
image: plugins/gitea-release
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
settings:
|
||||||
|
api_key:
|
||||||
|
from_secret: gitea_token
|
||||||
|
base_url: https://gitea.zionetrix.net
|
||||||
|
note: dist/release_notes.md
|
||||||
|
files:
|
||||||
|
- dist/*
|
||||||
|
- deb_dist/*.deb
|
||||||
|
checksum:
|
||||||
|
- md5
|
||||||
|
- sha512
|
||||||
|
|
||||||
|
publish-apt:
|
||||||
|
group: publish
|
||||||
|
image: brenard/aptly-publish
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
settings:
|
||||||
|
api_url:
|
||||||
|
from_secret: apt_api_url
|
||||||
|
api_username:
|
||||||
|
from_secret: apt_api_username
|
||||||
|
api_password:
|
||||||
|
from_secret: apt_api_password
|
||||||
|
repo_name:
|
||||||
|
from_secret: apt_repo_name
|
||||||
|
path: deb_dist
|
||||||
|
source_name: mylib
|
25
README.md
25
README.md
|
@ -35,35 +35,35 @@ Just run `pip install git+https://gitea.zionetrix.net/bn8/python-mylib.git`
|
||||||
|
|
||||||
Just run `python setup.py install`
|
Just run `python setup.py install`
|
||||||
|
|
||||||
**Note:** This project could previously use as independent python files (not as module). This old version is keep in _legacy_ git branch (not maintained).
|
**Note:** This project could previously use as independent python files (not as module). This old version is keep in *legacy* git branch (not maintained).
|
||||||
|
|
||||||
## Include libs
|
## Include libs
|
||||||
|
|
||||||
- **mylib.email.EmailClient:** An email client to forge (eventually using template) and send email via a SMTP server
|
* **mylib.email.EmailClient:** An email client to forge (eventually using template) and send email via a SMTP server
|
||||||
- **mylib.ldap.LdapServer:** A small lib to make requesting LDAP server easier. It's also provide some helper functions to deal with LDAP date string.
|
* **mylib.ldap.LdapServer:** A small lib to make requesting LDAP server easier. It's also provide some helper functions to deal with LDAP date string.
|
||||||
- **mylib.mysql.MyDB:** An extra small lib to remember me how to interact with MySQL/MariaDB database
|
* **mylib.mysql.MyDB:** An extra small lib to remember me how to interact with MySQL/MariaDB database
|
||||||
- **mylib.pgsql.PgDB:** An small lib to remember me how to interact with PostgreSQL database. **Warning:** The insert/update/delete/select methods demonstrate how to forge raw SQL request, but **it's a bad idea**: Prefer using prepared query.
|
* **mylib.pgsql.PgDB:** An small lib to remember me how to interact with PostgreSQL database. **Warning:** The insert/update/delete/select methods demonstrate how to forge raw SQL request, but **it's a bad idea**: Prefer using prepared query.
|
||||||
- **mylib.opening_hours:** A set of helper functions to deal with french opening hours (including normal opening hours, exceptional closure and nonworking public holidays).
|
* **mylib.opening_hours:** A set of helper functions to deal with french opening hours (including normal opening hours, exceptional closure and nonworking public holidays).
|
||||||
- **mylib.pbar.Pbar:** A small lib for progress bar
|
* **mylib.pbar.Pbar:** A small lib for progress bar
|
||||||
- **mylib.report.Report:** A small lib to implement logging based email report send at exit
|
* **mylib.report.Report:** A small lib to implement logging based email report send at exit
|
||||||
|
|
||||||
To know how to use these libs, you can take a look on _mylib.scripts_ content or in _tests_ directory.
|
To know how to use these libs, you can take a look on *mylib.scripts* content or in *tests* directory.
|
||||||
|
|
||||||
## Code Style
|
## Code Style
|
||||||
|
|
||||||
[pylint](https://pypi.org/project/pylint/) is used to check for errors and enforces a coding standard, using those parameters:
|
[pylint](https://pypi.org/project/pylint/) is used to check for errors and enforces a coding standard, using thoses parameters:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pylint --extension-pkg-whitelist=cx_Oracle
|
pylint --extension-pkg-whitelist=cx_Oracle
|
||||||
```
|
```
|
||||||
|
|
||||||
[flake8](https://pypi.org/project/flake8/) is also used to check for errors and enforces a coding standard, using those parameters:
|
[flake8](https://pypi.org/project/flake8/) is also used to check for errors and enforces a coding standard, using thoses parameters:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
flake8 --max-line-length=100
|
flake8 --max-line-length=100
|
||||||
```
|
```
|
||||||
|
|
||||||
[black](https://pypi.org/project/black/) is used to format the code, using those parameters:
|
[black](https://pypi.org/project/black/) is used to format the code, using thoses parameters:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
black --target-version py37 --line-length 100
|
black --target-version py37 --line-length 100
|
||||||
|
@ -83,6 +83,7 @@ pyupgrade --keep-percent-format --py37-plus
|
||||||
|
|
||||||
**Note:** There is `.pre-commit-config.yaml` to use [pre-commit](https://pre-commit.com/) to automatically run these tools before commits. After cloning the repository, execute `pre-commit install` to install the git hook.
|
**Note:** There is `.pre-commit-config.yaml` to use [pre-commit](https://pre-commit.com/) to automatically run these tools before commits. After cloning the repository, execute `pre-commit install` to install the git hook.
|
||||||
|
|
||||||
|
|
||||||
## Copyright
|
## Copyright
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Benjamin Renard <brenard@zionetrix.net>
|
Copyright (c) 2013-2021 Benjamin Renard <brenard@zionetrix.net>
|
||||||
|
|
2
build.sh
2
build.sh
|
@ -81,7 +81,7 @@ cd deb_dist/mylib-$VERSION
|
||||||
|
|
||||||
if [ -z "$DEBIAN_CODENAME" ]
|
if [ -z "$DEBIAN_CODENAME" ]
|
||||||
then
|
then
|
||||||
echo "Retrieve debian codename using lsb_release..."
|
echo "Retreive debian codename using lsb_release..."
|
||||||
DEBIAN_CODENAME=$( lsb_release -c -s )
|
DEBIAN_CODENAME=$( lsb_release -c -s )
|
||||||
[ $( lsb_release -r -s ) -ge 9 ] && DEBIAN_CODENAME="${DEBIAN_CODENAME}-ee"
|
[ $( lsb_release -r -s ) -ge 9 ] && DEBIAN_CODENAME="${DEBIAN_CODENAME}-ee"
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
""" Some really common helper functions """
|
""" Some really common helper functions """
|
||||||
|
|
||||||
#
|
#
|
||||||
# Pretty formatting helpers
|
# Pretty formating helpers
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ def increment_prefix(prefix):
|
||||||
|
|
||||||
|
|
||||||
def pretty_format_value(value, encoding="utf8", prefix=None):
|
def pretty_format_value(value, encoding="utf8", prefix=None):
|
||||||
"""Returned pretty formatted value to display"""
|
"""Returned pretty formated value to display"""
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
return pretty_format_dict(value, encoding=encoding, prefix=prefix)
|
return pretty_format_dict(value, encoding=encoding, prefix=prefix)
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
|
@ -27,10 +27,10 @@ def pretty_format_value(value, encoding="utf8", prefix=None):
|
||||||
|
|
||||||
def pretty_format_value_in_list(value, encoding="utf8", prefix=None):
|
def pretty_format_value_in_list(value, encoding="utf8", prefix=None):
|
||||||
"""
|
"""
|
||||||
Returned pretty formatted value to display in list
|
Returned pretty formated value to display in list
|
||||||
|
|
||||||
That method will prefix value with line return and incremented prefix
|
That method will prefix value with line return and incremented prefix
|
||||||
if pretty formatted value contains line return.
|
if pretty formated value contains line return.
|
||||||
"""
|
"""
|
||||||
prefix = prefix if prefix else ""
|
prefix = prefix if prefix else ""
|
||||||
value = pretty_format_value(value, encoding, prefix)
|
value = pretty_format_value(value, encoding, prefix)
|
||||||
|
@ -41,7 +41,7 @@ def pretty_format_value_in_list(value, encoding="utf8", prefix=None):
|
||||||
|
|
||||||
|
|
||||||
def pretty_format_dict(value, encoding="utf8", prefix=None):
|
def pretty_format_dict(value, encoding="utf8", prefix=None):
|
||||||
"""Returned pretty formatted dict to display"""
|
"""Returned pretty formated dict to display"""
|
||||||
prefix = prefix if prefix else ""
|
prefix = prefix if prefix else ""
|
||||||
result = []
|
result = []
|
||||||
for key in sorted(value.keys()):
|
for key in sorted(value.keys()):
|
||||||
|
@ -53,7 +53,7 @@ def pretty_format_dict(value, encoding="utf8", prefix=None):
|
||||||
|
|
||||||
|
|
||||||
def pretty_format_list(row, encoding="utf8", prefix=None):
|
def pretty_format_list(row, encoding="utf8", prefix=None):
|
||||||
"""Returned pretty formatted list to display"""
|
"""Returned pretty formated list to display"""
|
||||||
prefix = prefix if prefix else ""
|
prefix = prefix if prefix else ""
|
||||||
result = []
|
result = []
|
||||||
for idx, values in enumerate(row):
|
for idx, values in enumerate(row):
|
||||||
|
|
|
@ -465,7 +465,7 @@ class PasswordOption(StringOption):
|
||||||
|
|
||||||
service_name = self._keyring_service_name
|
service_name = self._keyring_service_name
|
||||||
username = self._keyring_username
|
username = self._keyring_username
|
||||||
log.debug("Retrieve password %s for username=%s from keyring", service_name, username)
|
log.debug("Retreive password %s for username=%s from keyring", service_name, username)
|
||||||
value = keyring.get_password(service_name, username)
|
value = keyring.get_password(service_name, username)
|
||||||
|
|
||||||
if value is None:
|
if value is None:
|
||||||
|
@ -757,7 +757,7 @@ class Config: # pylint: disable=too-many-instance-attributes
|
||||||
self.sections[name] = ConfigSection(self, name, **kwargs)
|
self.sections[name] = ConfigSection(self, name, **kwargs)
|
||||||
if loaded_callback:
|
if loaded_callback:
|
||||||
self._loaded_callbacks.append(loaded_callback)
|
self._loaded_callbacks.append(loaded_callback)
|
||||||
# If configuration is already loaded, execute callback immediately
|
# If configuration is already loaded, execute callback immediatly
|
||||||
if self._filepath or self.options:
|
if self._filepath or self.options:
|
||||||
self._loaded()
|
self._loaded()
|
||||||
return self.sections[name]
|
return self.sections[name]
|
||||||
|
@ -1155,7 +1155,7 @@ class Config: # pylint: disable=too-many-instance-attributes
|
||||||
dest="validate",
|
dest="validate",
|
||||||
help=(
|
help=(
|
||||||
"Validate configuration: initialize application to test if provided parameters"
|
"Validate configuration: initialize application to test if provided parameters"
|
||||||
" works.\n\nNote: Validation will occurred after configuration file creation or"
|
" works.\n\nNote: Validation will occured after configuration file creation or"
|
||||||
" update. On error, re-run with -O/--overwrite parameter to fix it."
|
" update. On error, re-run with -O/--overwrite parameter to fix it."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -1197,7 +1197,7 @@ class Config: # pylint: disable=too-many-instance-attributes
|
||||||
if options.validate:
|
if options.validate:
|
||||||
validate()
|
validate()
|
||||||
else:
|
else:
|
||||||
print(f"Error occurred creating configuration file {options.config}")
|
print(f"Error occured creating configuration file {options.config}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
@ -1282,7 +1282,7 @@ class ConfigurableObject:
|
||||||
raise ConfigException(f"No configuration name defined for {__name__}")
|
raise ConfigException(f"No configuration name defined for {__name__}")
|
||||||
|
|
||||||
def _get_option(self, option, default=None, required=False):
|
def _get_option(self, option, default=None, required=False):
|
||||||
"""Retrieve option value"""
|
"""Retreive option value"""
|
||||||
if self._kwargs and option in self._kwargs:
|
if self._kwargs and option in self._kwargs:
|
||||||
return self._kwargs[option]
|
return self._kwargs[option]
|
||||||
|
|
||||||
|
@ -1302,7 +1302,7 @@ class ConfigurableObject:
|
||||||
|
|
||||||
def set_default(self, option, default_value):
|
def set_default(self, option, default_value):
|
||||||
"""Set option default value"""
|
"""Set option default value"""
|
||||||
assert option in self._defaults, f"Unknown option {option}"
|
assert option in self._defaults, f"Unkown option {option}"
|
||||||
self._defaults[option] = default_value
|
self._defaults[option] = default_value
|
||||||
|
|
||||||
def set_defaults(self, **default_values):
|
def set_defaults(self, **default_values):
|
||||||
|
@ -1394,7 +1394,7 @@ class ConfigurableObject:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# If Config provided, use it's get_option() method to obtain a global just_try parameter
|
# If Config provided, use it's get_option() method to obtain a global just_try parameter
|
||||||
# value with a default to False, otherwise always false
|
# value with a defaut to False, otherwise always false
|
||||||
return self._config.get_option("just_try", default=False) if self._config else False
|
return self._config.get_option("just_try", default=False) if self._config else False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ class DBFailToConnect(DBException, RuntimeError):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, uri):
|
def __init__(self, uri):
|
||||||
super().__init__("An error occurred during database connection ({uri})", uri=uri)
|
super().__init__("An error occured during database connection ({uri})", uri=uri)
|
||||||
|
|
||||||
|
|
||||||
class DBDuplicatedSQLParameter(DBException, KeyError):
|
class DBDuplicatedSQLParameter(DBException, KeyError):
|
||||||
|
|
|
@ -239,7 +239,7 @@ class EmailClient(
|
||||||
msg["Date"] = email.utils.formatdate(None, True)
|
msg["Date"] = email.utils.formatdate(None, True)
|
||||||
encoding = encoding if encoding else self._get_option("encoding")
|
encoding = encoding if encoding else self._get_option("encoding")
|
||||||
if template:
|
if template:
|
||||||
assert template in self.templates, f"Unknown template {template}"
|
assert template in self.templates, f"Unknwon template {template}"
|
||||||
# Handle subject from template
|
# Handle subject from template
|
||||||
if not subject:
|
if not subject:
|
||||||
assert self.templates[template].get(
|
assert self.templates[template].get(
|
||||||
|
@ -251,7 +251,7 @@ class EmailClient(
|
||||||
else self.templates[template]["subject"].format(**template_vars)
|
else self.templates[template]["subject"].format(**template_vars)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Put HTML part in last one to preferred it
|
# Put HTML part in last one to prefered it
|
||||||
parts = []
|
parts = []
|
||||||
if self.templates[template].get("text"):
|
if self.templates[template].get("text"):
|
||||||
if isinstance(self.templates[template]["text"], MakoTemplate):
|
if isinstance(self.templates[template]["text"], MakoTemplate):
|
||||||
|
@ -322,7 +322,7 @@ class EmailClient(
|
||||||
catch_addr = self._get_option("catch_all_addr")
|
catch_addr = self._get_option("catch_all_addr")
|
||||||
if catch_addr:
|
if catch_addr:
|
||||||
log.debug(
|
log.debug(
|
||||||
"Catch email originally send to %s (CC:%s, BCC:%s) to %s",
|
"Catch email originaly send to %s (CC:%s, BCC:%s) to %s",
|
||||||
", ".join(recipients),
|
", ".join(recipients),
|
||||||
", ".join(cc) if isinstance(cc, list) else cc,
|
", ".join(cc) if isinstance(cc, list) else cc,
|
||||||
", ".join(bcc) if isinstance(bcc, list) else bcc,
|
", ".join(bcc) if isinstance(bcc, list) else bcc,
|
||||||
|
|
|
@ -211,7 +211,7 @@ class LdapServer:
|
||||||
result_page_control = rctrl
|
result_page_control = rctrl
|
||||||
break
|
break
|
||||||
|
|
||||||
# If PagedResultsControl answer not detected, paged search
|
# If PagedResultsControl answer not detected, paged serach
|
||||||
if not result_page_control:
|
if not result_page_control:
|
||||||
self._error(
|
self._error(
|
||||||
"LdapServer - Server ignores RFC2696 control, paged search can not works",
|
"LdapServer - Server ignores RFC2696 control, paged search can not works",
|
||||||
|
@ -238,7 +238,7 @@ class LdapServer:
|
||||||
page_control.cookie = result_page_control.cookie
|
page_control.cookie = result_page_control.cookie
|
||||||
|
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
"LdapServer - Paged search end: %d object(s) retrieved in %d page(s) of %d object(s)",
|
"LdapServer - Paged search end: %d object(s) retreived in %d page(s) of %d object(s)",
|
||||||
len(ret),
|
len(ret),
|
||||||
pages_count,
|
pages_count,
|
||||||
pagesize,
|
pagesize,
|
||||||
|
@ -379,12 +379,12 @@ class LdapServer:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_dn(obj):
|
def get_dn(obj):
|
||||||
"""Retrieve an on object DN from its entry in LDAP search result"""
|
"""Retreive an on object DN from its entry in LDAP search result"""
|
||||||
return obj[0][0]
|
return obj[0][0]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_attr(obj, attr, all_values=None, default=None, decode=False):
|
def get_attr(obj, attr, all_values=None, default=None, decode=False):
|
||||||
"""Retrieve an on object attribute value(s) from the object entry in LDAP search result"""
|
"""Retreive an on object attribute value(s) from the object entry in LDAP search result"""
|
||||||
if attr not in obj:
|
if attr not in obj:
|
||||||
for k in obj:
|
for k in obj:
|
||||||
if k.lower() == attr.lower():
|
if k.lower() == attr.lower():
|
||||||
|
@ -437,7 +437,7 @@ class LdapClient:
|
||||||
self.initialize()
|
self.initialize()
|
||||||
|
|
||||||
def _get_option(self, option, default=None, required=False):
|
def _get_option(self, option, default=None, required=False):
|
||||||
"""Retrieve option value"""
|
"""Retreive option value"""
|
||||||
if self._options and hasattr(self._options, self._options_prefix + option):
|
if self._options and hasattr(self._options, self._options_prefix + option):
|
||||||
return getattr(self._options, self._options_prefix + option)
|
return getattr(self._options, self._options_prefix + option)
|
||||||
|
|
||||||
|
@ -500,7 +500,7 @@ class LdapClient:
|
||||||
self.config = loaded_config
|
self.config = loaded_config
|
||||||
uri = self._get_option("uri", required=True)
|
uri = self._get_option("uri", required=True)
|
||||||
binddn = self._get_option("binddn")
|
binddn = self._get_option("binddn")
|
||||||
log.info("Connect to LDAP server %s as %s", uri, binddn if binddn else "anonymous")
|
log.info("Connect to LDAP server %s as %s", uri, binddn if binddn else "annonymous")
|
||||||
self._conn = LdapServer(
|
self._conn = LdapServer(
|
||||||
uri,
|
uri,
|
||||||
dn=binddn,
|
dn=binddn,
|
||||||
|
@ -553,7 +553,7 @@ class LdapClient:
|
||||||
:param attr: The attribute name
|
:param attr: The attribute name
|
||||||
:param all_values: If True, all values of the attribute will be
|
:param all_values: If True, all values of the attribute will be
|
||||||
returned instead of the first value only
|
returned instead of the first value only
|
||||||
(optional, default: False)
|
(optinal, default: False)
|
||||||
"""
|
"""
|
||||||
if attr not in obj:
|
if attr not in obj:
|
||||||
for k in obj:
|
for k in obj:
|
||||||
|
@ -582,7 +582,7 @@ class LdapClient:
|
||||||
:param name: The object type name
|
:param name: The object type name
|
||||||
:param filterstr: The LDAP filter to use to search objects on LDAP directory
|
:param filterstr: The LDAP filter to use to search objects on LDAP directory
|
||||||
:param basedn: The base DN of the search
|
:param basedn: The base DN of the search
|
||||||
:param attrs: The list of attribute names to retrieve
|
:param attrs: The list of attribute names to retreive
|
||||||
:param key_attr: The attribute name or 'dn' to use as key in result
|
:param key_attr: The attribute name or 'dn' to use as key in result
|
||||||
(optional, if leave to None, the result will be a list)
|
(optional, if leave to None, the result will be a list)
|
||||||
:param warn: If True, a warning message will be logged if no object is found
|
:param warn: If True, a warning message will be logged if no object is found
|
||||||
|
@ -594,7 +594,7 @@ class LdapClient:
|
||||||
(optional, default: see LdapServer.paged_search)
|
(optional, default: see LdapServer.paged_search)
|
||||||
"""
|
"""
|
||||||
if name in self._cached_objects:
|
if name in self._cached_objects:
|
||||||
log.debug("Retrieved %s objects from cache", name)
|
log.debug("Retreived %s objects from cache", name)
|
||||||
else:
|
else:
|
||||||
assert self._conn or self.initialize()
|
assert self._conn or self.initialize()
|
||||||
log.debug(
|
log.debug(
|
||||||
|
@ -643,7 +643,7 @@ class LdapClient:
|
||||||
:param object_name: The object name (only use in log messages)
|
:param object_name: The object name (only use in log messages)
|
||||||
:param filterstr: The LDAP filter to use to search the object on LDAP directory
|
:param filterstr: The LDAP filter to use to search the object on LDAP directory
|
||||||
:param basedn: The base DN of the search
|
:param basedn: The base DN of the search
|
||||||
:param attrs: The list of attribute names to retrieve
|
:param attrs: The list of attribute names to retreive
|
||||||
:param warn: If True, a warning message will be logged if no object is found
|
:param warn: If True, a warning message will be logged if no object is found
|
||||||
in LDAP directory (otherwise, it will be just a debug message)
|
in LDAP directory (otherwise, it will be just a debug message)
|
||||||
(optional, default: True)
|
(optional, default: True)
|
||||||
|
@ -855,7 +855,7 @@ class LdapClient:
|
||||||
Update an object
|
Update an object
|
||||||
|
|
||||||
:param ldap_obj: The original LDAP object
|
:param ldap_obj: The original LDAP object
|
||||||
:param changes: The changes to make on LDAP object (as formatted by get_changes() method)
|
:param changes: The changes to make on LDAP object (as formated by get_changes() method)
|
||||||
:param protected_attrs: An optional list of protected attributes
|
:param protected_attrs: An optional list of protected attributes
|
||||||
:param rdn_attr: The LDAP object RDN attribute (to detect renaming, default: auto-detected)
|
:param rdn_attr: The LDAP object RDN attribute (to detect renaming, default: auto-detected)
|
||||||
:param rdn_attr: Enable relax modification server control (optional, default: false)
|
:param rdn_attr: Enable relax modification server control (optional, default: false)
|
||||||
|
@ -915,7 +915,7 @@ class LdapClient:
|
||||||
# Otherwise, update object DN
|
# Otherwise, update object DN
|
||||||
ldap_obj["dn"] = new_dn
|
ldap_obj["dn"] = new_dn
|
||||||
else:
|
else:
|
||||||
log.debug("%s: No change detected on RDN attribute %s", ldap_obj["dn"], rdn_attr)
|
log.debug("%s: No change detected on RDN attibute %s", ldap_obj["dn"], rdn_attr)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self._just_try:
|
if self._just_try:
|
||||||
|
@ -1103,7 +1103,7 @@ def format_date(value, from_timezone=None, to_timezone=None, naive=True):
|
||||||
(optional, default : server local timezone)
|
(optional, default : server local timezone)
|
||||||
:param to_timezone: The timezone used in LDAP (optional, default : UTC)
|
:param to_timezone: The timezone used in LDAP (optional, default : UTC)
|
||||||
:param naive: Use naive datetime : do not handle timezone conversion before
|
:param naive: Use naive datetime : do not handle timezone conversion before
|
||||||
formatting and return datetime as UTC (because LDAP required a
|
formating and return datetime as UTC (because LDAP required a
|
||||||
timezone)
|
timezone)
|
||||||
"""
|
"""
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
|
|
|
@ -29,7 +29,7 @@ Mapping configuration
|
||||||
'join': '[glue]', # If present, sources values will be join using the "glue"
|
'join': '[glue]', # If present, sources values will be join using the "glue"
|
||||||
|
|
||||||
# Alternative mapping
|
# Alternative mapping
|
||||||
'or': { [map configuration] } # If this mapping case does not retrieve any value, try to
|
'or': { [map configuration] } # If this mapping case does not retreive any value, try to
|
||||||
# get value(s) with this other mapping configuration
|
# get value(s) with this other mapping configuration
|
||||||
},
|
},
|
||||||
'[dst key 2]': {
|
'[dst key 2]': {
|
||||||
|
|
|
@ -41,7 +41,7 @@ class MyDB(DB):
|
||||||
)
|
)
|
||||||
except Error as err:
|
except Error as err:
|
||||||
log.fatal(
|
log.fatal(
|
||||||
"An error occurred during MySQL database connection (%s@%s:%s).",
|
"An error occured during MySQL database connection (%s@%s:%s).",
|
||||||
self._user,
|
self._user,
|
||||||
self._host,
|
self._host,
|
||||||
self._db,
|
self._db,
|
||||||
|
|
|
@ -31,7 +31,7 @@ class OracleDB(DB):
|
||||||
self._conn = cx_Oracle.connect(user=self._user, password=self._pwd, dsn=self._dsn)
|
self._conn = cx_Oracle.connect(user=self._user, password=self._pwd, dsn=self._dsn)
|
||||||
except cx_Oracle.Error as err:
|
except cx_Oracle.Error as err:
|
||||||
log.fatal(
|
log.fatal(
|
||||||
"An error occurred during Oracle database connection (%s@%s).",
|
"An error occured during Oracle database connection (%s@%s).",
|
||||||
self._user,
|
self._user,
|
||||||
self._dsn,
|
self._dsn,
|
||||||
exc_info=1,
|
exc_info=1,
|
||||||
|
|
|
@ -45,7 +45,7 @@ class PgDB(DB):
|
||||||
)
|
)
|
||||||
except psycopg2.Error as err:
|
except psycopg2.Error as err:
|
||||||
log.fatal(
|
log.fatal(
|
||||||
"An error occurred during Postgresql database connection (%s@%s, database=%s).",
|
"An error occured during Postgresql database connection (%s@%s, database=%s).",
|
||||||
self._user,
|
self._user,
|
||||||
self._host,
|
self._host,
|
||||||
self._db,
|
self._db,
|
||||||
|
@ -71,7 +71,7 @@ class PgDB(DB):
|
||||||
return True
|
return True
|
||||||
except psycopg2.Error:
|
except psycopg2.Error:
|
||||||
log.error(
|
log.error(
|
||||||
'An error occurred setting Postgresql database connection encoding to "%s"',
|
'An error occured setting Postgresql database connection encoding to "%s"',
|
||||||
enc,
|
enc,
|
||||||
exc_info=1,
|
exc_info=1,
|
||||||
)
|
)
|
||||||
|
@ -126,7 +126,7 @@ class PgDB(DB):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#
|
#
|
||||||
# Deprecated helpers
|
# Depreated helpers
|
||||||
#
|
#
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -96,7 +96,7 @@ class Report(ConfigurableObject): # pylint: disable=useless-object-inheritance
|
||||||
self.send_at_exit()
|
self.send_at_exit()
|
||||||
|
|
||||||
def get_handler(self):
|
def get_handler(self):
|
||||||
"""Retrieve logging handler"""
|
"""Retreive logging handler"""
|
||||||
return self.handler
|
return self.handler
|
||||||
|
|
||||||
def write(self, msg):
|
def write(self, msg):
|
||||||
|
|
|
@ -31,7 +31,7 @@ def init_logging(options, name, report=None):
|
||||||
|
|
||||||
|
|
||||||
def get_default_opt_value(config, default_config, key):
|
def get_default_opt_value(config, default_config, key):
|
||||||
"""Retrieve default option value from config or default config dictionaries"""
|
"""Retreive default option value from config or default config dictionaries"""
|
||||||
if config and key in config:
|
if config and key in config:
|
||||||
return config[key]
|
return config[key]
|
||||||
return default_config.get(key)
|
return default_config.get(key)
|
||||||
|
|
|
@ -47,7 +47,7 @@ def main(argv=None): # pylint: disable=too-many-locals,too-many-statements
|
||||||
sftp.connect()
|
sftp.connect()
|
||||||
atexit.register(sftp.close)
|
atexit.register(sftp.close)
|
||||||
|
|
||||||
log.debug("Create temporary file")
|
log.debug("Create tempory file")
|
||||||
test_content = b"Juste un test."
|
test_content = b"Juste un test."
|
||||||
tmp_dir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
|
tmp_dir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
|
||||||
tmp_file = os.path.join(
|
tmp_file = os.path.join(
|
||||||
|
|
|
@ -116,13 +116,13 @@ class SFTPClient(ConfigurableObject):
|
||||||
if self.initial_directory:
|
if self.initial_directory:
|
||||||
log.debug("Initial remote directory: '%s'", self.initial_directory)
|
log.debug("Initial remote directory: '%s'", self.initial_directory)
|
||||||
else:
|
else:
|
||||||
log.debug("Fail to retrieve remote directory, use empty string instead")
|
log.debug("Fail to retreive remote directory, use empty string instead")
|
||||||
self.initial_directory = ""
|
self.initial_directory = ""
|
||||||
|
|
||||||
def get_file(self, remote_filepath, local_filepath):
|
def get_file(self, remote_filepath, local_filepath):
|
||||||
"""Retrieve a file from SFTP server"""
|
"""Retrieve a file from SFTP server"""
|
||||||
self.connect()
|
self.connect()
|
||||||
log.debug("Retrieve file '%s' to '%s'", remote_filepath, local_filepath)
|
log.debug("Retreive file '%s' to '%s'", remote_filepath, local_filepath)
|
||||||
return self.sftp_client.get(remote_filepath, local_filepath) is None
|
return self.sftp_client.get(remote_filepath, local_filepath) is None
|
||||||
|
|
||||||
def open_file(self, remote_filepath, mode="r"):
|
def open_file(self, remote_filepath, mode="r"):
|
||||||
|
|
|
@ -35,7 +35,7 @@ class TelltaleFile:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_update(self):
|
def last_update(self):
|
||||||
"""Retrieve last update datetime of the telltall file"""
|
"""Retreive last update datetime of the telltall file"""
|
||||||
try:
|
try:
|
||||||
return datetime.datetime.fromtimestamp(os.stat(self.filepath).st_mtime)
|
return datetime.datetime.fromtimestamp(os.stat(self.filepath).st_mtime)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
|
2
tests.sh
2
tests.sh
|
@ -32,7 +32,7 @@ do
|
||||||
set -x
|
set -x
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
usage "Unknown parameter '$OPT'"
|
usage "Unkown parameter '$OPT'"
|
||||||
esac
|
esac
|
||||||
let idx=idx+1
|
let idx=idx+1
|
||||||
done
|
done
|
||||||
|
|
|
@ -10,7 +10,7 @@ import pytest
|
||||||
|
|
||||||
from mylib.config import BooleanOption, Config, ConfigSection, StringOption
|
from mylib.config import BooleanOption, Config, ConfigSection, StringOption
|
||||||
|
|
||||||
tested = {}
|
runned = {}
|
||||||
|
|
||||||
|
|
||||||
def test_config_init_default_args():
|
def test_config_init_default_args():
|
||||||
|
@ -58,24 +58,24 @@ def test_add_section_with_callback():
|
||||||
config = Config("Test app")
|
config = Config("Test app")
|
||||||
name = "test_section"
|
name = "test_section"
|
||||||
|
|
||||||
global tested
|
global runned
|
||||||
tested["test_add_section_with_callback"] = False
|
runned["test_add_section_with_callback"] = False
|
||||||
|
|
||||||
def test_callback(loaded_config):
|
def test_callback(loaded_config):
|
||||||
global tested
|
global runned
|
||||||
assert loaded_config == config
|
assert loaded_config == config
|
||||||
assert tested["test_add_section_with_callback"] is False
|
assert runned["test_add_section_with_callback"] is False
|
||||||
tested["test_add_section_with_callback"] = True
|
runned["test_add_section_with_callback"] = True
|
||||||
|
|
||||||
section = config.add_section(name, loaded_callback=test_callback)
|
section = config.add_section(name, loaded_callback=test_callback)
|
||||||
assert isinstance(section, ConfigSection)
|
assert isinstance(section, ConfigSection)
|
||||||
assert test_callback in config._loaded_callbacks
|
assert test_callback in config._loaded_callbacks
|
||||||
assert tested["test_add_section_with_callback"] is False
|
assert runned["test_add_section_with_callback"] is False
|
||||||
|
|
||||||
config.parse_arguments_options(argv=[], create=False)
|
config.parse_arguments_options(argv=[], create=False)
|
||||||
assert tested["test_add_section_with_callback"] is True
|
assert runned["test_add_section_with_callback"] is True
|
||||||
assert test_callback in config._loaded_callbacks_executed
|
assert test_callback in config._loaded_callbacks_executed
|
||||||
# Try to execute again to verify callback is not tested again
|
# Try to execute again to verify callback is not runned again
|
||||||
config._loaded()
|
config._loaded()
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,21 +84,21 @@ def test_add_section_with_callback_already_loaded():
|
||||||
name = "test_section"
|
name = "test_section"
|
||||||
config.parse_arguments_options(argv=[], create=False)
|
config.parse_arguments_options(argv=[], create=False)
|
||||||
|
|
||||||
global tested
|
global runned
|
||||||
tested["test_add_section_with_callback_already_loaded"] = False
|
runned["test_add_section_with_callback_already_loaded"] = False
|
||||||
|
|
||||||
def test_callback(loaded_config):
|
def test_callback(loaded_config):
|
||||||
global tested
|
global runned
|
||||||
assert loaded_config == config
|
assert loaded_config == config
|
||||||
assert tested["test_add_section_with_callback_already_loaded"] is False
|
assert runned["test_add_section_with_callback_already_loaded"] is False
|
||||||
tested["test_add_section_with_callback_already_loaded"] = True
|
runned["test_add_section_with_callback_already_loaded"] = True
|
||||||
|
|
||||||
section = config.add_section(name, loaded_callback=test_callback)
|
section = config.add_section(name, loaded_callback=test_callback)
|
||||||
assert isinstance(section, ConfigSection)
|
assert isinstance(section, ConfigSection)
|
||||||
assert tested["test_add_section_with_callback_already_loaded"] is True
|
assert runned["test_add_section_with_callback_already_loaded"] is True
|
||||||
assert test_callback in config._loaded_callbacks
|
assert test_callback in config._loaded_callbacks
|
||||||
assert test_callback in config._loaded_callbacks_executed
|
assert test_callback in config._loaded_callbacks_executed
|
||||||
# Try to execute again to verify callback is not tested again
|
# Try to execute again to verify callback is not runned again
|
||||||
config._loaded()
|
config._loaded()
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue