Browse Source

style: added lint checks

pull/307/head
Richard Nemeth 4 years ago
parent
commit
7122490a4f
No known key found for this signature in database GPG Key ID: 21C39470DF3DEC39
  1. 37
      .circleci/config.yml
  2. 29
      .github/workflows/lint.yaml
  3. 2
      .gitignore
  4. 16
      .pre-commit-config.yaml
  5. 15
      CONTRIBUTING.md
  6. 70
      docs/source/conf.py
  7. 1
      docs/source/index.rst
  8. 2
      requirements.txt
  9. 4
      setup.py
  10. 35
      test_keycloak_init.sh
  11. 14
      tests/conftest.py
  12. 189
      tests/test_connection.py
  13. 9
      tests/test_keycloak_admin.py
  14. 4
      tox.env
  15. 32
      tox.ini

37
.circleci/config.yml

@ -1,37 +0,0 @@
version: 2
jobs:
build:
docker:
- image: circleci/python:3.6.1
working_directory: ~/repo
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "requirements.txt" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run:
name: install dependencies
command: |
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt
- save_cache:
paths:
- ./venv
key: v1-dependencies-{{ checksum "requirements.txt" }}
- run:
name: run tests
command: |
. venv/bin/activate
python3 -m unittest discover
- store_artifacts:
path: test-reports
destination: test-reports

29
.github/workflows/lint.yaml

@ -0,0 +1,29 @@
name: Python package linting
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox
- name: Check linting, formatting
run: |
tox -e check

2
.gitignore

@ -105,3 +105,5 @@ main.py
main2.py main2.py
s3air-authz-config.json s3air-authz-config.json
.vscode .vscode
.docker
.pytest_cache

16
.pre-commit-config.yaml

@ -0,0 +1,16 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v1.2.0
hooks:
- id: conventional-pre-commit
stages: [ commit-msg ]
args: [ ] # optional: list of Conventional Commits types to allow

15
CONTRIBUTING.md

@ -0,0 +1,15 @@
# Contributing
Commits to this project must adhere to the [Conventional Commits
specification](https://www.conventionalcommits.org/en/v1.0.0/) that will allow
us to automate version bumps and changelog entry creation.
After cloning this repository, you must install the pre-commit hook for
conventional commits:
```sh
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install pre-commit
pre-commit install --install-hooks -t pre-commit -t pre-push -t commit-msg
```

70
docs/source/conf.py

@ -32,37 +32,37 @@ import sphinx_rtd_theme
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.viewcode',
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx.ext.viewcode",
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# The suffix(es) of source filenames. # The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string: # You can specify multiple suffix as a list of string:
# #
# source_suffix = ['.rst', '.md'] # source_suffix = ['.rst', '.md']
source_suffix = '.rst'
source_suffix = ".rst"
# The master toctree document. # The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project. # General information about the project.
project = 'python-keycloak'
copyright = '2017, Marcos Pereira'
author = 'Marcos Pereira'
project = "python-keycloak"
copyright = "2017, Marcos Pereira"
author = "Marcos Pereira"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.27.0'
version = "0.27.0"
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.27.0'
release = "0.27.0"
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
@ -74,13 +74,13 @@ language = None
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path # This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
add_function_parentheses = False add_function_parentheses = False
add_module_names = True add_module_names = True
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = "sphinx"
# If true, `todo` and `todoList` produce output, else they produce nothing. # If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True todo_include_todos = True
@ -91,7 +91,7 @@ todo_include_todos = True
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
# #
html_theme = 'sphinx_rtd_theme'
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
@ -103,7 +103,7 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]
html_use_smartypants = False html_use_smartypants = False
@ -116,7 +116,7 @@ html_show_copyright = True
# #
# This is required for the alabaster theme # This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
#html_sidebars = {
# html_sidebars = {
# '**': [ # '**': [
# 'about.html', # 'about.html',
# 'navigation.html', # 'navigation.html',
@ -124,13 +124,13 @@ html_show_copyright = True
# 'searchbox.html', # 'searchbox.html',
# 'donate.html', # 'donate.html',
# ] # ]
#}
# }
# -- Options for HTMLHelp output ------------------------------------------ # -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'python-keycloakdoc'
htmlhelp_basename = "python-keycloakdoc"
# -- Options for LaTeX output --------------------------------------------- # -- Options for LaTeX output ---------------------------------------------
@ -139,15 +139,12 @@ latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
# #
# 'papersize': 'letterpaper', # 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
# #
# 'pointsize': '10pt', # 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
# #
# 'preamble': '', # 'preamble': '',
# Latex figure (float) alignment # Latex figure (float) alignment
# #
# 'figure_align': 'htbp', # 'figure_align': 'htbp',
@ -157,8 +154,13 @@ latex_elements = {
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [ latex_documents = [
(master_doc, 'python-keycloak.tex', 'python-keycloak Documentation',
'Marcos Pereira', 'manual'),
(
master_doc,
"python-keycloak.tex",
"python-keycloak Documentation",
"Marcos Pereira",
"manual",
),
] ]
@ -166,10 +168,7 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'python-keycloak', 'python-keycloak Documentation',
[author], 1)
]
man_pages = [(master_doc, "python-keycloak", "python-keycloak Documentation", [author], 1)]
# -- Options for Texinfo output ------------------------------------------- # -- Options for Texinfo output -------------------------------------------
@ -178,10 +177,13 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
(master_doc, 'python-keycloak', 'python-keycloak Documentation',
author, 'python-keycloak', 'One line description of project.',
'Miscellaneous'),
(
master_doc,
"python-keycloak",
"python-keycloak Documentation",
author,
"python-keycloak",
"One line description of project.",
"Miscellaneous",
),
] ]

1
docs/source/index.rst

@ -304,4 +304,3 @@ Main methods::
# Delete the key # Delete the key
keycloak_admin.delete_component(component['id']) keycloak_admin.delete_component(component['id'])

2
requirements.txt

@ -5,3 +5,5 @@ twine==1.13.0
jose~=1.0.0 jose~=1.0.0
setuptools~=54.2.0 setuptools~=54.2.0
urllib3>=1.26.5 urllib3>=1.26.5
pytest>=7.1.2
pytest-cov>=3.0.0

4
setup.py

@ -16,8 +16,8 @@ setup(
description="python-keycloak is a Python package providing access to the Keycloak API.", description="python-keycloak is a Python package providing access to the Keycloak API.",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
packages=["keycloak", "keycloak.authorization", "keycloak.tests"],
install_requires=["requests>=2.20.0", "python-jose>=1.4.0", "urllib>=1.26.0"],
packages=["keycloak", "keycloak.authorization"],
install_requires=["requests>=2.20.0", "python-jose>=1.4.0", "urllib3>=1.26.0"],
tests_require=["httmock>=1.2.5"], tests_require=["httmock>=1.2.5"],
classifiers=[ classifiers=[
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",

35
test_keycloak_init.sh

@ -0,0 +1,35 @@
#!/usr/bin/env bash
CMD_ARGS=$1
KEYCLOAK_DOCKER_IMAGE="quay.io/keycloak/keycloak:latest"
echo "${CMD_ARGS}"
function keycloak_stop() {
docker stop unittest_keycloak &> /dev/null
docker rm unittest_keycloak &> /dev/null
}
function keycloak_start() {
echo "Starting keycloak docker container"
docker run -d --name unittest_keycloak -e KEYCLOAK_ADMIN="${KEYCLOAK_ADMIN}" -e KEYCLOAK_ADMIN_PASSWORD="${KEYCLOAK_ADMIN_PASSWORD}" -p "${KEYCLOAK_PORT}:8080" "${KEYCLOAK_DOCKER_IMAGE}" start-dev
SECONDS=0
until curl localhost:8080; do
sleep 5;
if [ ${SECONDS} -gt 180 ]; then
echo "Timeout exceeded";
exit 1;
fi
done
}
# Ensuring that postgres is stopped in case of CTRL-C
trap keycloak_stop err exit
keycloak_stop # In case it did not shut down correctly last time.
keycloak_start
eval ${CMD_ARGS}
RETURN_VALUE=$?
exit ${RETURN_VALUE}

14
tests/conftest.py

@ -0,0 +1,14 @@
import os
import pytest
@pytest.fixture
def env():
class KeycloakTestEnv(object):
KEYCLOAK_HOST = os.environ["KEYCLOAK_HOST"]
KEYCLOAK_PORT = os.environ["KEYCLOAK_PORT"]
KEYCLOAK_ADMIN = os.environ["KEYCLOAK_ADMIN"]
KEYCLOAK_ADMIN_PASSWORD = os.environ["KEYCLOAK_ADMIN_PASSWORD"]
return KeycloakTestEnv()

189
tests/test_connection.py

@ -1,189 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2017 Marcos Pereira <marcospereira.mpj@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from unittest import mock
from httmock import urlmatch, response, HTTMock, all_requests
from keycloak import KeycloakAdmin, KeycloakOpenID
from keycloak.connection import ConnectionManager
try:
import unittest
except ImportError:
import unittest2 as unittest
class TestConnection(unittest.TestCase):
def setUp(self):
self._conn = ConnectionManager(base_url="http://localhost/", headers={}, timeout=60)
@all_requests
def response_content_success(self, url, request):
headers = {"content-type": "application/json"}
content = b"response_ok"
return response(200, content, headers, None, 5, request)
def test_raw_get(self):
with HTTMock(self.response_content_success):
resp = self._conn.raw_get("/known_path")
self.assertEqual(resp.content, b"response_ok")
self.assertEqual(resp.status_code, 200)
def test_raw_post(self):
@urlmatch(path="/known_path", method="post")
def response_post_success(url, request):
headers = {"content-type": "application/json"}
content = "response".encode("utf-8")
return response(201, content, headers, None, 5, request)
with HTTMock(response_post_success):
resp = self._conn.raw_post("/known_path", {"field": "value"})
self.assertEqual(resp.content, b"response")
self.assertEqual(resp.status_code, 201)
def test_raw_put(self):
@urlmatch(netloc="localhost", path="/known_path", method="put")
def response_put_success(url, request):
headers = {"content-type": "application/json"}
content = "response".encode("utf-8")
return response(200, content, headers, None, 5, request)
with HTTMock(response_put_success):
resp = self._conn.raw_put("/known_path", {"field": "value"})
self.assertEqual(resp.content, b"response")
self.assertEqual(resp.status_code, 200)
def test_raw_get_fail(self):
@urlmatch(netloc="localhost", path="/known_path", method="get")
def response_get_fail(url, request):
headers = {"content-type": "application/json"}
content = "404 page not found".encode("utf-8")
return response(404, content, headers, None, 5, request)
with HTTMock(response_get_fail):
resp = self._conn.raw_get("/known_path")
self.assertEqual(resp.content, b"404 page not found")
self.assertEqual(resp.status_code, 404)
def test_raw_post_fail(self):
@urlmatch(netloc="localhost", path="/known_path", method="post")
def response_post_fail(url, request):
headers = {"content-type": "application/json"}
content = str(["Start can't be blank"]).encode("utf-8")
return response(404, content, headers, None, 5, request)
with HTTMock(response_post_fail):
resp = self._conn.raw_post("/known_path", {"field": "value"})
self.assertEqual(resp.content, str(["Start can't be blank"]).encode("utf-8"))
self.assertEqual(resp.status_code, 404)
def test_raw_put_fail(self):
@urlmatch(netloc="localhost", path="/known_path", method="put")
def response_put_fail(url, request):
headers = {"content-type": "application/json"}
content = str(["Start can't be blank"]).encode("utf-8")
return response(404, content, headers, None, 5, request)
with HTTMock(response_put_fail):
resp = self._conn.raw_put("/known_path", {"field": "value"})
self.assertEqual(resp.content, str(["Start can't be blank"]).encode("utf-8"))
self.assertEqual(resp.status_code, 404)
def test_add_param_headers(self):
self._conn.add_param_headers("test", "value")
self.assertEqual(self._conn.headers, {"test": "value"})
def test_del_param_headers(self):
self._conn.add_param_headers("test", "value")
self._conn.del_param_headers("test")
self.assertEqual(self._conn.headers, {})
def test_clean_param_headers(self):
self._conn.add_param_headers("test", "value")
self.assertEqual(self._conn.headers, {"test": "value"})
self._conn.clean_headers()
self.assertEqual(self._conn.headers, {})
def test_exist_param_headers(self):
self._conn.add_param_headers("test", "value")
self.assertTrue(self._conn.exist_param_headers("test"))
self.assertFalse(self._conn.exist_param_headers("test_no"))
def test_get_param_headers(self):
self._conn.add_param_headers("test", "value")
self.assertTrue(self._conn.exist_param_headers("test"))
self.assertFalse(self._conn.exist_param_headers("test_no"))
def test_get_headers(self):
self._conn.add_param_headers("test", "value")
self.assertEqual(self._conn.headers, {"test": "value"})
def test_KeycloakAdmin_custom_header(self):
class FakeToken:
@staticmethod
def get(string_val):
return "faketoken"
fake_token = FakeToken()
with mock.patch.object(
KeycloakOpenID, "__init__", return_value=None
) as mock_keycloak_open_id:
with mock.patch(
"keycloak.keycloak_openid.KeycloakOpenID.token", return_value=fake_token
):
with mock.patch(
"keycloak.connection.ConnectionManager.__init__", return_value=None
) as mock_connection_manager:
with mock.patch(
"keycloak.connection.ConnectionManager.__del__", return_value=None
) as mock_connection_manager_delete:
server_url = "https://localhost/auth/"
username = "admin"
password = "secret"
realm_name = "master"
headers = {"Custom": "test-custom-header"}
KeycloakAdmin(
server_url=server_url,
username=username,
password=password,
realm_name=realm_name,
verify=False,
custom_headers=headers,
)
mock_keycloak_open_id.assert_called_with(
server_url=server_url,
realm_name=realm_name,
client_id="admin-cli",
client_secret_key=None,
verify=False,
custom_headers=headers,
)
expected_header = {
"Authorization": "Bearer faketoken",
"Content-Type": "application/json",
"Custom": "test-custom-header",
}
mock_connection_manager.assert_called_with(
base_url=server_url, headers=expected_header, timeout=60, verify=False
)
mock_connection_manager_delete.assert_called_once_with()

9
tests/test_keycloak_admin.py

@ -0,0 +1,9 @@
from keycloak import KeycloakAdmin
def test_keycloak_admin_init(env):
KeycloakAdmin(
server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
username=env.KEYCLOAK_ADMIN,
password=env.KEYCLOAK_ADMIN_PASSWORD,
)

4
tox.env

@ -0,0 +1,4 @@
KEYCLOAK_ADMIN=admin
KEYCLOAK_ADMIN_PASSWORD=admin
KEYCLOAK_HOST={env:KEYCLOAK_HOST:localhost}
KEYCLOAK_PORT=8080

32
tox.ini

@ -0,0 +1,32 @@
[tox]
envlist = check, docs, tests
[testenv]
install_command = pip install {opts} {packages}
[testenv:check]
deps =
black
isort
flake8
commands =
black --check --diff keycloak tests docs
isort -c --df keycloak tests docs
flake8 keycloak tests docs
[testenv:tests]
deps =
-rrequirements.txt
setenv = file|tox.env
commands =
./test_keycloak_init.sh "pytest -v --cov=keycloak --cov-report term-missing {posargs}"
[flake8]
max-line-length = 99
[black]
line-length = 99
[isort]
line_length = 99
profile = "black"
Loading…
Cancel
Save