Skip to content

Commit 0a1480d

Browse files
committed
linting, docs, and reporting
Signed-off-by: Bryant Finney <[email protected]>
1 parent d8a3fdf commit 0a1480d

9 files changed

+140
-25
lines changed

.gitlab-ci.yml

+56-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ stages:
1111
- build
1212
- lint
1313
- test
14+
- docs
1415
- deploy
1516

1617
.dev image: &dev-image
@@ -53,6 +54,17 @@ include:
5354
project: poetry-tools/ci-cd
5455
ref: main
5556

57+
- file: poethepoet.yml
58+
inputs:
59+
<<: *dev-image
60+
POETRY_VIRTUALENVS_CREATE: "false"
61+
before-script: poetry install --only-root
62+
poetry-install: "false"
63+
stage: docs
64+
task: docs
65+
project: poetry-tools/ci-cd
66+
ref: main
67+
5668
- file: publish.yml
5769
inputs: *dev-image
5870
project: poetry-tools/ci-cd
@@ -87,9 +99,39 @@ include:
8799

88100
✔️ poetry check: *needs_docker-buildx-bake
89101

90-
🪶 poe lint: *needs_docker-buildx-bake
91-
92-
🪶 poe test: *needs_docker-buildx-bake
102+
🪶 poe lint:
103+
<<: *needs_docker-buildx-bake
104+
105+
artifacts:
106+
paths: [docs]
107+
reports:
108+
codequality: [docs/reports/pylint-gitlab.json]
109+
untracked: true
110+
when: always
111+
112+
🪶 poe test:
113+
<<: *needs_docker-buildx-bake
114+
115+
artifacts:
116+
paths: [docs]
117+
reports:
118+
coverage_report:
119+
coverage_format: cobertura
120+
path: docs/reports/coverage.xml
121+
junit: [docs/reports/pytest.xml]
122+
untracked: true
123+
when: always
124+
coverage: '/Total coverage.*\s+(\d+(?:\.\d+)?)%/'
125+
126+
🪶 poe docs:
127+
artifacts:
128+
untracked: true
129+
when: on_success
130+
needs:
131+
- job: 🪶 poe lint
132+
artifacts: true
133+
- job: 🪶 poe test
134+
artifacts: true
93135

94136
🌐 poetry publish gitlab:
95137
needs: &poetry-publish-needs
@@ -101,6 +143,17 @@ include:
101143
rules:
102144
- if: $CI_COMMIT_REF_PROTECTED == "true"
103145

146+
pages:
147+
artifacts:
148+
paths: [public]
149+
needs:
150+
- 🌐 poetry publish gitlab
151+
- job: 🪶 poe docs
152+
artifacts: true
153+
rules: [if: $CI_COMMIT_REF_PROTECTED == "true"]
154+
script: mv ./docs ./public
155+
stage: deploy
156+
104157
🌐 poetry publish testpypi:
105158
needs: *poetry-publish-needs
106159
rules: &only-protected-tags

.mdlrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rules "~MD013"

README.md

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# A Springy Little Configuration Reader
22

3+
[![latest release](https://gitlab.com/bryant.finney/pyspry/-/badges/release.svg)](https://gitlab.com/bryant.finney/pyspry/-/releases)
4+
[![pipeline status](https://gitlab.com/bryant.finney/pyspry/badges/main/pipeline.svg)](https://gitlab.com/bryant.finney/pyspry/-/commits/main)
5+
[![coverage report](https://gitlab.com/bryant.finney/pyspry/badges/main/coverage.svg)](https://gitlab.com/bryant.finney/pyspry/-/commits/main)
6+
[![Maintainability](https://api.codeclimate.com/v1/badges/996a01b1ab2df27571d5/maintainability)](https://codeclimate.com/github/bryant-finney/pyspry/maintainability)
7+
[![pylint](docs/reports/pylint.svg)](https://)
8+
[![PyPI version](https://badge.fury.io/py/pyspry.svg)](https://badge.fury.io/py/pyspry)
9+
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
10+
311
Influenced by [Spring Boot's YAML configuration features](https://docs.spring.io/spring-boot/docs/1.1.0.M1/reference/html/boot-features-external-config.html#boot-features-external-config-yaml),
412
this library reads system configuration settings from environment variables and YAML files.
513

@@ -18,12 +26,14 @@ configuration settings. These can be accessed through the `pyspry.settings` modu
1826

1927
For example, consider the following `config.yml` file:
2028

21-
DATABASES:
22-
default:
23-
AUTOCOMMIT: true
24-
NAME: db.sqlite3
25-
DEBUG: true
26-
DEFAULT_CHARSET: utf-8
29+
```yml
30+
DATABASES:
31+
default:
32+
AUTOCOMMIT: true
33+
NAME: db.sqlite3
34+
DEBUG: true
35+
DEFAULT_CHARSET: utf-8
36+
```
2737
2838
These configuration settings can be accessed as follows:
2939
@@ -137,7 +147,7 @@ GLOBAL OPTIONS
137147
--version Print the version and exit
138148
-v, --verbose Increase command output (repeatable)
139149
-q, --quiet Decrease command output (repeatable)
140-
-d, --dry-run Print the task contents but don't actaully run it
150+
-d, --dry-run Print the task contents but don't actually run it
141151
--root PATH Specify where to find the pyproject.toml
142152
--ansi Force enable ANSI output
143153
--no-ansi Force disable ANSI output

poetry.lock

+32-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+22-4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ django = "^4.1"
6767
django-extensions = "^3.2"
6868
anybadge = "^1.14"
6969
pylint = "^2.17"
70+
pylint-gitlab = "^1.2.0"
7071

7172
[tool.poetry.group.pre-commit.dependencies]
7273
# note: these must be synchronized with `.pre-commit-config.yaml`
@@ -208,6 +209,17 @@ cmd = """
208209
--color always
209210
"""
210211

212+
[[tool.poe.tasks.lint.sequence]]
213+
cmd = """pylint src"""
214+
215+
[[tool.poe.tasks.lint.sequence]]
216+
shell = """
217+
grep -oE 'Your code has been rated at [0-9.]+' docs/reports/pylint-report.txt |
218+
rev | cut -d' ' -f1 | rev |
219+
xargs -I{} anybadge --overwrite --label pylint --value {} \
220+
--file=docs/reports/pylint.svg 7=red 8=orange 9=yellow 10=green
221+
"""
222+
211223
[[tool.poe.tasks.lint.sequence]]
212224
shell = "safety check --continue-on-error --full-report"
213225

@@ -344,7 +356,7 @@ function-naming-style = "snake_case"
344356
# style. If left empty, function names will be checked with the set naming style.
345357
# function-rgx =
346358
# Good variable names which should always be accepted, separated by a comma.
347-
good-names = ["e", "f", "i", "j", "k", "ex", "Run", "_"]
359+
good-names = ["e", "f", "i", "j", "k", "v", "ex", "Run", "_"]
348360
# Good variable names regexes, separated by a comma. If names match any regex,
349361
# they will always be accepted
350362
# good-names-rgxs =
@@ -427,7 +439,7 @@ min-public-methods = 2
427439

428440
[tool.pylint.exceptions]
429441
# Exceptions that will emit a warning when caught.
430-
overgeneral-exceptions = ["BaseException", "Exception"]
442+
overgeneral-exceptions = ["builtins.BaseException", "builtins.Exception"]
431443

432444
[tool.pylint.format]
433445
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
@@ -509,6 +521,7 @@ disable = [
509521
"useless-suppression",
510522
"deprecated-pragma",
511523
"use-symbolic-message-instead",
524+
"too-few-public-methods",
512525
]
513526
# Enable the message, report, category or checker with the given id(s). You can
514527
# either give multiple identifier separated by comma (,) or put this option
@@ -557,9 +570,14 @@ evaluation = "max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refact
557570
# Set the output format. Available formats are text, parseable, colorized, json
558571
# and msvs (visual studio). You can also give a reporter class, e.g.
559572
# mypackage.mymodule.MyReporterClass.
560-
# output-format =
573+
output-format = """\
574+
colorized,\
575+
pylint_gitlab.GitlabCodeClimateReporter:docs/reports/pylint-gitlab.json,\
576+
pylint_gitlab.GitlabPagesHtmlReporter:docs/reports/pylint-gitlab.html,\
577+
text:docs/reports/pylint-report.txt\
578+
"""
561579
# Tells whether to display a full report or only the messages.
562-
# reports =
580+
reports = "y"
563581
# Activate the evaluation score.
564582
score = true
565583

src/pyspry/base.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ def __init__(self, config: dict[str, Any], environ: dict[str, str], prefix: str)
202202
# the value must just be a simple string
203203
parsed = value
204204

205-
if isinstance(parsed, list) or isinstance(parsed, dict):
205+
if isinstance(parsed, (dict, list)):
206206
env[key] = NestedDict(parsed)
207207
else:
208208
env[key] = parsed
@@ -263,10 +263,10 @@ def __getattr_override(self, name: str) -> Any:
263263

264264
try:
265265
attr_val = self.__config[attr_name]
266-
except KeyError:
266+
except KeyError as e:
267267
raise AttributeError(
268268
f"'{self.__class__.__name__}' object has no attribute '{attr_name}'"
269-
)
269+
) from e
270270

271271
return (
272272
attr_val.serialize(strip_prefix=self.prefix)

src/pyspry/conftest.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
from pyspry import settings as pyspry_settings
1717
from pyspry.base import Settings
1818

19-
config_yaml = """
19+
# pylint: disable=redefined-outer-name
20+
21+
CONFIG_YAML = """
2022
APP_NAME_ATTR_A: [1, 2, 3]
2123
APP_NAME_EXAMPLE_PARAM: a string!
2224
APP_NAME_ATTR_B_K: 0
@@ -38,7 +40,7 @@ def config() -> dict[str, Any]:
3840
Returns:
3941
dict[str, Any]: the dummy config for `NestedDict` objects
4042
"""
41-
config: dict[str, Any] = yaml.safe_load(config_yaml)
43+
config: dict[str, Any] = yaml.safe_load(CONFIG_YAML)
4244
return config
4345

4446

src/pyspry/nested_dict.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class NestedKeysView(KeysView[KT]):
2828
... )
2929
>>> list(v)
3030
['A0', 'A0_B00', 'A0_B00_C000', 'A0_B00_C001', 'A0_B01', 'A0_B01_C010', 'A0_B01_C011', 'A1', 'A1_B10', 'A1_B10_C100', 'A1_B10_C101', 'A1_B11', 'A1_B11_C110', 'A1_B11_C111']
31-
"""
31+
""" # pylint: disable=line-too-long
3232

3333
_mapping: Mapping[KT, Any]
3434
prefix: str
@@ -60,7 +60,7 @@ def __iter__(self) -> Iterator[str]: # type: ignore[override]
6060
# TODO: drop after support for 3.8 is dropped
6161
else: # pragma: no cover
6262
# stdlib
63-
from collections.abc import KeysView, Mapping
63+
from collections.abc import KeysView, Mapping # pylint: disable=ungrouped-imports
6464

6565
class NestedKeysView(KeysView): # type: ignore[no-redef,type-arg]
6666
"""Override `KeysView` to recurse through nesting.
@@ -73,7 +73,7 @@ class NestedKeysView(KeysView): # type: ignore[no-redef,type-arg]
7373
... )
7474
>>> list(v)
7575
['A0', 'A0_B00', 'A0_B00_C000', 'A0_B00_C001', 'A0_B01', 'A0_B01_C010', 'A0_B01_C011', 'A1', 'A1_B10', 'A1_B10_C100', 'A1_B10_C101', 'A1_B11', 'A1_B11_C110', 'A1_B11_C111']
76-
"""
76+
""" # pylint: disable=line-too-long
7777

7878
_mapping: Mapping # type: ignore[type-arg]
7979

@@ -272,7 +272,7 @@ def __setitem__(self, name: str, value: Any) -> None:
272272
@classmethod
273273
def _ensure_structure(cls, data: dict[Any, Any]) -> None:
274274
for key, maybe_nested in list(data.items()):
275-
if isinstance(maybe_nested, dict) or isinstance(maybe_nested, list):
275+
if isinstance(maybe_nested, (dict, list)):
276276
data[key] = NestedDict(maybe_nested)
277277

278278
def get_first_match(self, nested_name: str) -> Any:

tests/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
from __future__ import annotations
33

44
# local
5-
from pyspry.conftest import config, config_path, config_yaml, settings # noqa: F401
5+
from pyspry.conftest import CONFIG_YAML, config, config_path, settings # noqa: F401

0 commit comments

Comments
 (0)