Skip to content
Snippets Groups Projects
Commit f5855de4 authored by Bastien Abadie's avatar Bastien Abadie
Browse files

Merge branch 'initial' into 'master'

Initial import from arkindex_common

See merge request teklia/python-toolbox!1
parents 299ae3bb 9079fbc7
No related branches found
No related tags found
1 merge request!1Initial import from arkindex_common
Pipeline #128469 failed
[flake8]
max-line-length = 120
exclude=build,.cache,.eggs,.git,src/zeep,front
# Flake8 ignores multiple errors by default;
# the only interesting ignore is W503, which goes against PEP8.
# See https://lintlyci.github.io/Flake8Rules/rules/W503.html
ignore = E203,E501,W503
\ No newline at end of file
**.pyc
.tox/
*.egg-info
stages:
- test
- build
- release
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
paths:
- .cache/pip
linter:
stage: test
image: python:3
cache:
paths:
- .cache/pip
- .cache/pre-commit
except:
- schedules
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
PRE_COMMIT_HOME: "$CI_PROJECT_DIR/.cache/pre-commit"
before_script:
- pip install pre-commit
script:
- pre-commit run -a
tests:
stage: test
image: python:3.7
cache:
paths:
- .cache/pip
except:
- schedules
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
before_script:
- pip install tox
script:
- tox -e py37-unit
release-pypi:
stage: release
only:
- tags
environment:
name: pypi
url: https://pypi.org/project/teklia-toolbox
before_script:
- pip install twine setuptools wheel
- echo "[distutils]" > ~/.pypirc
- echo "index-servers =" >> ~/.pypirc
- echo " pypi" >> ~/.pypirc
- echo "[pypi]" >> ~/.pypirc
- echo "repository=https://upload.pypi.org/legacy/" >> ~/.pypirc
- echo "username=$PYPI_release_USERNAME" >> ~/.pypirc
- echo "password=$PYPI_release_PASSWORD" >> ~/.pypirc
script:
- python setup.py sdist bdist_wheel
- twine upload dist/* -r pypi
release-notes:
stage: release
image: registry.gitlab.com/teklia/devops:latest
only:
- tags
script:
- devops release-notes
bump-python-deps:
stage: build
image: registry.gitlab.com/teklia/devops:latest
only:
- schedules
script:
- devops python-deps requirements.txt base/requirements.txt tests-requirements.txt
[settings]
# Compatible with black
multi_line_output = 3
include_trailing_comma = True
force_grid_wrap = 0
use_parentheses = True
line_length = 120
default_section = FIRSTPARTY
known_first_party = arkindex_toolbox
known_third_party = setuptools,yaml
repos:
- repo: https://github.com/asottile/seed-isort-config
rev: v2.2.0
hooks:
- id: seed-isort-config
- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.21
hooks:
- id: isort
- repo: https://github.com/ambv/black
rev: 20.8b1
hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.3
hooks:
- id: flake8
additional_dependencies:
- 'flake8-coding==1.3.1'
- 'flake8-copyright==0.2.2'
- 'flake8-debugger==3.1.0'
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.1.0
hooks:
- id: check-ast
- id: check-docstring-first
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: check-symlinks
- id: debug-statements
- id: trailing-whitespace
- id: check-yaml
args: [--allow-multiple-documents]
- id: mixed-line-ending
- id: name-tests-test
args: ['--django']
- id: check-json
- id: requirements-txt-fixer
- repo: https://github.com/codespell-project/codespell
rev: v1.17.1
hooks:
- id: codespell
args: ['--write-changes']
- repo: meta
hooks:
- id: check-useless-excludes
default_language_version:
python: python3.7
include VERSION
include requirements.txt
0.1.0
PyYAML==5.3.1
setup.py 0 → 100644
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os.path
from setuptools import find_packages, setup
def requirements(path):
assert os.path.exists(path), "Missing requirements {}".format(path)
with open(path) as f:
return list(map(str.strip, f.read().splitlines()))
with open("VERSION") as f:
VERSION = f.read()
install_requires = requirements("requirements.txt")
setup(
name="teklia-toolbox",
version=VERSION,
author="Teklia",
author_email="contact@teklia.com",
python_requires=">=3.7",
install_requires=install_requires,
packages=find_packages(),
include_package_data=True,
)
# -*- coding: utf-8 -*-
import json
import os
import sys
from collections import namedtuple
from collections.abc import Mapping
from pathlib import Path
import yaml
Option = namedtuple("Option", ["type", "default"])
# Used as a default value in `ConfigParser.add_option(default=UNSET)`
# because default=None implies that the option is optional
UNSET = object()
def _all_checks():
"""
Prevents checking for path existence when running unit tests
or other dev-related operations.
This is the same as settings.ALL_CHECKS, but since the configuration
is accessed before settings are initialized, it has to be copied here.
This is made as a method to make mocking in unit tests much simpler
than with a module-level constant.
"""
os.environ.get("ALL_CHECKS") == "true" or "runserver" in sys.argv
def file_path(data):
path = Path(data).resolve()
if _all_checks():
assert path.exists(), f"{path} does not exist"
assert path.is_file(), f"{path} is not a file"
return path
def dir_path(data):
path = Path(data).resolve()
if _all_checks():
assert path.exists(), f"{path} does not exist"
assert path.is_dir(), f"{path} is not a directory"
return path
class ConfigurationError(ValueError):
def __init__(self, errors, *args, **kwargs):
super().__init__(*args, **kwargs)
self.errors = errors
def __str__(self):
return json.dumps(self.errors)
def __repr__(self):
return "{}({!s})".format(self.__class__.__name__, self)
class ConfigParser(object):
def __init__(self, allow_extra_keys=True):
"""
:param allow_extra_keys bool: Ignore extra unspecified keys instead
of causing errors.
"""
self.options = {}
self.allow_extra_keys = allow_extra_keys
def add_option(self, name, *, type=str, many=False, default=UNSET):
assert name not in self.options, f"{name} is an already defined option"
assert callable(type), "Option type must be callable"
if many:
self.options[name] = Option(lambda data: list(map(type, data)), default)
else:
self.options[name] = Option(type, default)
def add_subparser(self, *args, allow_extra_keys=True, **kwargs):
"""
Add a parser as a new option to this parser,
to allow finer control over nested configuration options.
"""
parser = ConfigParser(allow_extra_keys=allow_extra_keys)
self.add_option(*args, **kwargs, type=parser.parse_data)
return parser
def parse_data(self, data):
"""
Parse configuration data from a dict.
Will raise ConfigurationError if any error is detected.
"""
if not isinstance(data, Mapping):
raise ConfigurationError("Parser data must be a mapping")
parsed, errors = {}, {}
if not self.allow_extra_keys:
for name in data:
if name not in self.options:
errors[name] = "This option does not exist"
for name, option in self.options.items():
if name in data:
value = data[name]
elif option.default is UNSET:
errors[name] = "This option is required"
continue
elif option.default is None:
parsed[name] = None
continue
else:
value = option.default
try:
parsed[name] = option.type(value)
except ConfigurationError as e:
# Allow nested error dicts for nicer error messages
# with add_subparser
errors[name] = e.errors
except Exception as e:
errors[name] = str(e)
if errors:
raise ConfigurationError(errors)
return parsed
def parse(self, path, exist_ok=False):
if not path.is_file() and exist_ok:
# Act like the file is empty
return self.parse_data({})
with open(path) as f:
return self.parse_data(yaml.safe_load(f))
# -*- coding: utf-8 -*-
import datetime
from timeit import default_timer
class Timer(object):
"""
A context manager to help measure execution times
"""
def __init__(self):
self.timer = default_timer
def __enter__(self):
self.start = self.timer()
return self
def __exit__(self, *args):
end = self.timer()
self.elapsed = end - self.start
self.delta = datetime.timedelta(seconds=self.elapsed)
# -*- coding: utf-8 -*-
import time
from teklia_toolbox.time import Timer
def test_timer():
with Timer() as t:
time.sleep(0.05)
assert t.delta.total_seconds() >= 0.05
tox.ini 0 → 100644
[tox]
envlist = py37-unit
[testenv:py37-unit]
commands =
pytest
deps =
pytest
-rrequirements.txt
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment