Skip to content

Commit e64728a

Browse files
committed
[DEV] Cookiecutter Python Package v1.4.1
2 parents 6400565 + 0ea8ca7 commit e64728a

File tree

16 files changed

+223
-126
lines changed

16 files changed

+223
-126
lines changed

.prospector.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ ignore-paths:
1111
ignore-patterns:
1212
- (^|/)skip(this)?(/|$)
1313
- src/cookiecutter_python/{{ cookiecutter.project_slug }}/tests/smoke_test.py
14+
- src/cookiecutter_python/{{ cookiecutter.project_slug }}/src/{{ cookiecutter.pkg_name }}/__main__.py
15+
- src/cookiecutter_python/{{ cookiecutter.project_slug }}/src/{{ cookiecutter.pkg_name }}/cli.py
1416
autodetect: false
1517
max-line-length: 95
1618

@@ -50,10 +52,7 @@ bandit:
5052
frosted:
5153
run: false
5254

53-
pep8:
54-
run: false
55-
56-
pep257:
55+
pycodestyle:
5756
run: false
5857

5958
mypy:

.pylintrc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ disable=print-statement,
150150
unnecessary-lambda,
151151
inconsistent-return-statements,
152152
redundant-keyword-arg,
153-
not-callable
153+
not-callable,
154+
unsubscriptable-object,
155+
protected-access
154156

155157
# Enable the message, report, category or checker with the given id(s). You can
156158
# either give multiple identifier separated by comma (,) or put this option
@@ -225,9 +227,9 @@ notes=FIXME,
225227
contextmanager-decorators=contextlib.contextmanager
226228

227229
# List of members which are set dynamically and missed by pylint inference
228-
# system, and so shouldn't trigger E1101 when accessed. Python regular
230+
# system, and so shouldn't trigger E1101 (no-member) when accessed. Python regular
229231
# expressions are accepted.
230-
generated-members=_transform|self\.objects|self\._observers|subclasses
232+
generated-members=_transform|self\.objects|self\._observers|subclasses|InteractiveDialog\.create
231233

232234
# Tells whether missing members accessed in mixin class should be ignored. A
233235
# mixin class is detected if its name ends with "mixin" (case insensitive).

CHANGELOG.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22
Changelog
33
=========
44

5+
1.4.1 (2022-06-07)
6+
==================
7+
8+
Changes
9+
^^^^^^^
10+
11+
refactor
12+
""""""""
13+
- decouple dialog creation
14+
15+
chore
16+
"""""
17+
- satisfy prospector linter even better
18+
19+
520
1.4.0 (2022-06-06)
621
==================
722

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,9 @@ For more complex use cases, you can modify the Template and also leverage all of
196196

197197
.. Github Releases & Tags
198198
199-
.. |commits_since_specific_tag_on_master| image:: https://img.shields.io/github/commits-since/boromir674/cookiecutter-python-package/v1.4.0/master?color=blue&logo=github
199+
.. |commits_since_specific_tag_on_master| image:: https://img.shields.io/github/commits-since/boromir674/cookiecutter-python-package/v1.4.1/master?color=blue&logo=github
200200
:alt: GitHub commits since tagged version (branch)
201-
:target: https://github.com/boromir674/cookiecutter-python-package/compare/v1.4.0..master
201+
:target: https://github.com/boromir674/cookiecutter-python-package/compare/v1.4.1..master
202202

203203
.. |commits_since_latest_github_release| image:: https://img.shields.io/github/commits-since/boromir674/cookiecutter-python-package/latest?color=blue&logo=semver&sort=semver
204204
:alt: GitHub commits since latest release (by SemVer)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ build-backend = "poetry.core.masonry.api"
1010
## Also renders on pypi as 'subtitle'
1111
[tool.poetry]
1212
name = "cookiecutter_python"
13-
version = "1.4.0"
13+
version = "1.4.1"
1414
description = "Yet another modern Python Package (pypi) with emphasis in CI/CD and automation."
1515
authors = ["Konstantinos Lampridis <k.lampridis@hotmail.com>"]
1616
maintainers = ["Konstantinos Lampridis <k.lampridis@hotmail.com>"]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.4.0'
1+
__version__ = '1.4.1'
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import io
2+
import json
3+
import logging
4+
import typing as t
5+
from json import JSONDecodeError
6+
7+
import poyo
8+
9+
GivenInterpreters = t.Mapping[str, t.Sequence[str]]
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
def load_yaml(config_file) -> t.Mapping:
15+
# TODO use a proxy to load yaml
16+
with io.open(config_file, encoding='utf-8') as file_handle:
17+
try:
18+
yaml_dict = poyo.parse_string(file_handle.read())
19+
except poyo.exceptions.PoyoException as error:
20+
raise InvalidYamlFormatError(
21+
'Unable to parse YAML file {}. Error: {}' ''.format(config_file, error)
22+
) from error
23+
return yaml_dict
24+
25+
26+
def get_interpreters_from_yaml(config_file: str) -> t.Optional[GivenInterpreters]:
27+
"""Parse the 'interpreters' variable out of the user's config yaml file.
28+
29+
Args:
30+
config_file (str): path to the user's config yaml file
31+
32+
Raises:
33+
InvalidYamlFormatError: if yaml parser fails to load the user's config
34+
UserYamlDesignError: if yaml does not contain the 'default_context' key
35+
36+
Returns:
37+
GivenInterpreters: dictionary with intepreters as a sequence of strings,
38+
mapped to the 'supported-interpreters' key
39+
"""
40+
data = load_yaml(config_file)
41+
if 'default_context' not in data:
42+
raise UserYamlDesignError(
43+
"User config (is valid yaml but) does not contain a 'default_context' outer key!"
44+
)
45+
context = data['default_context']
46+
if 'interpreters' not in context:
47+
return None
48+
49+
try:
50+
interpreters_data = json.loads(context['interpreters'])
51+
except JSONDecodeError as error:
52+
logger.warning(
53+
"User's yaml config 'interpreters' value Error: %s",
54+
json.dumps(
55+
{
56+
'error': error,
57+
'message': "Expected json 'parasable' value for the 'interpreters' key",
58+
},
59+
sort_keys=True,
60+
indent=4,
61+
),
62+
)
63+
return None
64+
return interpreters_data
65+
66+
67+
class UserYamlDesignError(Exception):
68+
pass
69+
70+
71+
class InvalidYamlFormatError(Exception):
72+
pass
Lines changed: 12 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
import io
2-
import json
31
import logging
42
import os
53
import sys
64
import typing as t
75

8-
import poyo
9-
from cookiecutter.exceptions import InvalidConfiguration
10-
from requests.exceptions import ConnectionError, JSONDecodeError
6+
from requests.exceptions import ConnectionError
117

128
from cookiecutter_python.backend.check_pypi import check_pypi
139
from cookiecutter_python.backend.check_pypi_handler import handler
10+
from cookiecutter_python.backend.load_config import get_interpreters_from_yaml
11+
from cookiecutter_python.handle.interpreters_support import handle as get_interpreters
1412

1513
from .cookiecutter_proxy import cookiecutter
1614

@@ -19,94 +17,28 @@
1917
my_dir = os.path.dirname(os.path.realpath(__file__))
2018

2119

22-
def load_yaml(config_file) -> t.Mapping:
23-
# TODO use a proxy to load yaml
24-
with io.open(config_file, encoding='utf-8') as file_handle:
25-
try:
26-
yaml_dict = poyo.parse_string(file_handle.read())
27-
except poyo.exceptions.PoyoException as error:
28-
raise InvalidConfiguration(
29-
'Unable to parse YAML file {}. Error: {}' ''.format(config_file, error)
30-
) from error
31-
return yaml_dict
32-
33-
3420
GivenInterpreters = t.Mapping[str, t.Sequence[str]]
3521

3622

3723
def supported_interpreters(config_file, no_input) -> t.Optional[GivenInterpreters]:
3824
if not no_input: # interactive
3925
if sys.version_info < (3, 10):
40-
check_box_dialog(config_file=config_file)
41-
# else return None: let generator backend (ie cookiecutter) handle
42-
# receiving the 'supported-interpreters' information from user input
43-
# non-interactive
26+
return check_box_dialog(config_file=config_file)
27+
return None
4428
if config_file:
45-
try:
46-
return get_interpreters_from_yaml(config_file)
47-
except (
48-
InvalidConfiguration,
49-
UserConfigFormatError,
50-
NoInterpretersInUserConfigException,
51-
JSONDecodeError,
52-
):
53-
pass
29+
return get_interpreters_from_yaml(config_file)
5430
return None
5531

5632

5733
def check_box_dialog(config_file=None) -> GivenInterpreters:
58-
from cookiecutter_python.handle.interpreters_support import (
59-
handle as get_interpreters,
60-
)
61-
62-
defaults = None
34+
defaults: t.Optional[t.Sequence[str]] = None
6335
if config_file:
64-
try:
65-
defaults = get_interpreters_from_yaml(config_file)['supported-interpreters']
66-
except (
67-
InvalidConfiguration,
68-
UserConfigFormatError,
69-
NoInterpretersInUserConfigException,
70-
JSONDecodeError,
71-
):
72-
pass
73-
return get_interpreters(choices=defaults)
74-
75-
76-
def get_interpreters_from_yaml(config_file: str) -> GivenInterpreters:
77-
"""Parse the 'interpreters' variable out of the user's config yaml file.
78-
79-
Args:
80-
config_file (str): path to the user's config yaml file
81-
82-
Raises:
83-
InvalidConfiguration: if yaml parser fails to load the user's config
84-
UserConfigFormatError: if yaml doesn't contain the 'default_context' key
85-
NoInterpretersInUserConfigException: if yaml doesn't contain the
86-
'interpreters' key, under the 'default_context' key
87-
JSONDecodeError: if json parser fails to load the 'interpreters' value
88-
89-
Returns:
90-
GivenInterpreters: dictionary with intepreters as a sequence of strings,
91-
mapped to the 'supported-interpreters' key
92-
"""
93-
data = load_yaml(config_file)
94-
if 'default_context' not in data:
95-
raise UserConfigFormatError(
96-
"User config (is valid yaml but) does not contain a 'default_context' outer key!"
97-
)
98-
context = data['default_context']
99-
if 'interpreters' not in context:
100-
raise NoInterpretersInUserConfigException(
101-
"No 'interpreters' key found in user's config (under the 'default_context' key)."
36+
interpreters_data: t.Optional[GivenInterpreters] = get_interpreters_from_yaml(
37+
config_file
10238
)
103-
interpreters_data = json.loads(context['interpreters'])
104-
if 'supported-interpreters' not in interpreters_data:
105-
raise UserConfigFormatError(
106-
"User config (is valid yaml but) does not contain a "
107-
"'supported-interpreters' key in the 'interpreters' key"
108-
)
109-
return {'supported-interpreters': interpreters_data['supported-interpreters']}
39+
if interpreters_data:
40+
defaults = interpreters_data.get('supported-interpreters', None)
41+
return get_interpreters(choices=defaults)
11042

11143

11244
def generate(
@@ -174,11 +106,3 @@ def generate(
174106

175107
class CheckPypiError(Exception):
176108
pass
177-
178-
179-
class UserConfigFormatError(Exception):
180-
pass
181-
182-
183-
class NoInterpretersInUserConfigException(Exception):
184-
pass
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from . import lib # noqa
2+
from .dialog import InteractiveDialog
3+
4+
__all__ = ['InteractiveDialog']
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from abc import abstractmethod
2+
3+
from software_patterns import SubclassRegistry
4+
5+
6+
class InteractiveDialog(metaclass=SubclassRegistry):
7+
@abstractmethod
8+
def dialog(self, *args, **kwargs):
9+
raise NotImplementedError

0 commit comments

Comments
 (0)