Skip to content

Commit 776d585

Browse files
committed
refactor: Cleanup for pylint
1 parent fb87f94 commit 776d585

File tree

9 files changed

+91
-40
lines changed

9 files changed

+91
-40
lines changed

openapi_python_client/__init__.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from .utils import snake_case
2121

2222
if sys.version_info.minor < 8: # version did not exist before 3.8, need to use a backport
23+
# pylint: disable=import-error
2324
from importlib_metadata import version
2425
else:
2526
from importlib.metadata import version # type: ignore
@@ -28,6 +29,8 @@
2829

2930

3031
class MetaType(str, Enum):
32+
"""The types of metadata supported for project generation."""
33+
3134
NONE = "none"
3235
POETRY = "poetry"
3336
SETUP = "setup"
@@ -41,7 +44,9 @@ class MetaType(str, Enum):
4144
}
4245

4346

44-
class Project:
47+
class Project: # pylint: disable=too-many-instance-attributes
48+
"""Represents a Python project (the top level file-tree) to generate"""
49+
4550
def __init__(
4651
self,
4752
*,
@@ -129,15 +134,19 @@ def _reformat(self) -> None:
129134
shell=True,
130135
stdout=subprocess.PIPE,
131136
stderr=subprocess.PIPE,
137+
check=True,
132138
)
133139
subprocess.run(
134140
"isort .",
135141
cwd=self.project_dir,
136142
shell=True,
137143
stdout=subprocess.PIPE,
138144
stderr=subprocess.PIPE,
145+
check=True,
146+
)
147+
subprocess.run(
148+
"black .", cwd=self.project_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True
139149
)
140-
subprocess.run("black .", cwd=self.project_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
141150

142151
def _get_errors(self) -> Sequence[GeneratorError]:
143152
errors = []
@@ -263,7 +272,7 @@ def _build_api(self) -> None:
263272
module_path.write_text(endpoint_template.render(endpoint=endpoint), encoding=self.file_encoding)
264273

265274

266-
def _get_project_for_url_or_path(
275+
def _get_project_for_url_or_path( # pylint: disable=too-many-arguments
267276
url: Optional[str],
268277
path: Optional[Path],
269278
meta: MetaType,

openapi_python_client/cli.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,28 @@ def _process_config(path: Optional[pathlib.Path]) -> Config:
2727

2828
try:
2929
return Config.load_from_path(path=path)
30-
except: # noqa
31-
raise typer.BadParameter("Unable to parse config")
30+
except Exception as err:
31+
raise typer.BadParameter("Unable to parse config") from err
3232

3333

3434
# noinspection PyUnusedLocal
35+
# pylint: disable=unused-argument
3536
@app.callback(name="openapi-python-client")
3637
def cli(
3738
version: bool = typer.Option(False, "--version", callback=_version_callback, help="Print the version and exit"),
3839
) -> None:
3940
"""Generate a Python client from an OpenAPI JSON document"""
40-
pass
4141

4242

43-
def _print_parser_error(e: GeneratorError, color: str) -> None:
44-
typer.secho(e.header, bold=True, fg=color, err=True)
43+
def _print_parser_error(err: GeneratorError, color: str) -> None:
44+
typer.secho(err.header, bold=True, fg=color, err=True)
4545
typer.echo()
46-
if e.detail:
47-
typer.secho(e.detail, fg=color, err=True)
46+
if err.detail:
47+
typer.secho(err.detail, fg=color, err=True)
4848
typer.echo()
4949

50-
if isinstance(e, ParseError) and e.data is not None:
51-
formatted_data = pformat(e.data)
50+
if isinstance(err, ParseError) and err.data is not None:
51+
formatted_data = pformat(err.data)
5252
typer.secho(formatted_data, fg=color, err=True)
5353

5454
typer.echo()
@@ -111,6 +111,7 @@ def handle_errors(errors: Sequence[GeneratorError], fail_on_warning: bool = Fals
111111
CONFIG_OPTION = typer.Option(None, "--config", help="Path to the config file to use")
112112

113113

114+
# pylint: disable=too-many-arguments
114115
@app.command()
115116
def generate(
116117
url: Optional[str] = typer.Option(None, help="A URL to read the JSON from"),
@@ -133,9 +134,9 @@ def generate(
133134

134135
try:
135136
codecs.getencoder(file_encoding)
136-
except LookupError:
137+
except LookupError as err:
137138
typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED)
138-
raise typer.Exit(code=1)
139+
raise typer.Exit(code=1) from err
139140

140141
config = _process_config(config_path)
141142
errors = create_new_client(
@@ -149,6 +150,7 @@ def generate(
149150
handle_errors(errors, fail_on_warning)
150151

151152

153+
# pylint: disable=too-many-arguments
152154
@app.command()
153155
def update(
154156
url: Optional[str] = typer.Option(None, help="A URL to read the JSON from"),
@@ -171,9 +173,9 @@ def update(
171173

172174
try:
173175
codecs.getencoder(file_encoding)
174-
except LookupError:
176+
except LookupError as err:
175177
typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED)
176-
raise typer.Exit(code=1)
178+
raise typer.Exit(code=1) from err
177179

178180
config = _process_config(config_path)
179181
errors = update_existing_client(

openapi_python_client/config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,21 @@
66

77

88
class ClassOverride(BaseModel):
9+
"""An override of a single generated class.
10+
11+
See https://github.com/openapi-generators/openapi-python-client#class_overrides
12+
"""
13+
914
class_name: Optional[str] = None
1015
module_name: Optional[str] = None
1116

1217

1318
class Config(BaseModel):
19+
"""Contains any configurable values passed by the user.
20+
21+
See https://github.com/openapi-generators/openapi-python-client#configuration
22+
"""
23+
1424
class_overrides: Dict[str, ClassOverride] = {}
1525
project_name_override: Optional[str]
1626
package_name_override: Optional[str]

openapi_python_client/parser/openapi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def generate_operation_id(*, path: str, method: str) -> str:
8181
return f"{method}_{clean_path}"
8282

8383

84+
# pylint: disable=too-many-instance-attributes
8485
@dataclass
8586
class Endpoint:
8687
"""

openapi_python_client/schema/openapi_schema_pydantic/example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
class Example(BaseModel):
7-
""" Examples added to parameters / components to help clarify usage.
7+
"""Examples added to parameters / components to help clarify usage.
88
99
References:
1010
- https://swagger.io/docs/specification/adding-examples/

openapi_python_client/utils.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
from keyword import iskeyword
44
from typing import Any, List
55

6-
delimiters = " _-"
6+
DELIMITERS = " _-"
77

88

99
class PythonIdentifier(str):
1010
"""A string which has been validated / transformed into a valid identifier for Python"""
1111

1212
def __new__(cls, value: str, prefix: str) -> "PythonIdentifier":
13-
new_value = fix_reserved_words(fix_keywords(snake_case(sanitize(value))))
13+
new_value = fix_reserved_words(snake_case(sanitize(value)))
1414

1515
if not new_value.isidentifier():
1616
new_value = f"{prefix}{new_value}"
@@ -22,50 +22,60 @@ def __deepcopy__(self, _: Any) -> "PythonIdentifier":
2222

2323
def sanitize(value: str) -> str:
2424
"""Removes every character that isn't 0-9, A-Z, a-z, or a known delimiter"""
25-
return re.sub(rf"[^\w{delimiters}]+", "", value)
25+
return re.sub(rf"[^\w{DELIMITERS}]+", "", value)
2626

2727

2828
def split_words(value: str) -> List[str]:
2929
"""Split a string on words and known delimiters"""
3030
# We can't guess words if there is no capital letter
3131
if any(c.isupper() for c in value):
3232
value = " ".join(re.split("([A-Z]?[a-z]+)", value))
33-
return re.findall(rf"[^{delimiters}]+", value)
34-
35-
36-
def fix_keywords(value: str) -> str:
37-
if iskeyword(value):
38-
return f"{value}_"
39-
return value
33+
return re.findall(rf"[^{DELIMITERS}]+", value)
4034

4135

4236
RESERVED_WORDS = (set(dir(builtins)) | {"self"}) - {"type", "id"}
4337

4438

4539
def fix_reserved_words(value: str) -> str:
46-
if value in RESERVED_WORDS:
40+
"""
41+
Using reserved Python words as identifiers in generated code causes problems, so this function renames them.
42+
43+
Args:
44+
value: The identifier to-be that should be renamed if it's a reserved word.
45+
46+
Returns:
47+
`value` suffixed with `_` if it was a reserved word.
48+
"""
49+
if value in RESERVED_WORDS or iskeyword(value):
4750
return f"{value}_"
4851
return value
4952

5053

5154
def snake_case(value: str) -> str:
55+
"""Converts to snake_case"""
5256
words = split_words(sanitize(value))
53-
value = "_".join(words).lower()
54-
return fix_keywords(value)
57+
return "_".join(words).lower()
5558

5659

5760
def pascal_case(value: str) -> str:
61+
"""Converts to PascalCase"""
5862
words = split_words(sanitize(value))
5963
capitalized_words = (word.capitalize() if not word.isupper() else word for word in words)
60-
value = "".join(capitalized_words)
61-
return fix_keywords(value)
64+
return "".join(capitalized_words)
6265

6366

6467
def kebab_case(value: str) -> str:
68+
"""Converts to kebab-case"""
6569
words = split_words(sanitize(value))
66-
value = "-".join(words).lower()
67-
return fix_keywords(value)
70+
return "-".join(words).lower()
6871

6972

7073
def remove_string_escapes(value: str) -> str:
74+
"""Used when parsing string-literal defaults to prevent escaping the string to write arbitrary Python
75+
76+
**REMOVING OR CHANGING THE USAGE OF THIS FUNCTION HAS SECURITY IMPLICATIONS**
77+
78+
See Also:
79+
- https://github.com/openapi-generators/openapi-python-client/security/advisories/GHSA-9x4c-63pf-525f
80+
"""
7181
return value.replace('"', r"\"")

pyproject.toml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,20 @@ omit = ["openapi_python_client/templates/*"]
9999
max-line-length = 120
100100

101101
[tool.pylint.messages_control]
102-
disable = ["duplicate-code", "missing-module-docstring", "too-few-public-methods"]
102+
disable = [
103+
# DRY < MOIST
104+
"duplicate-code",
105+
# Sometimes necessary to prevent cycles
106+
"import-outside-toplevel",
107+
# Modules are mostly used for organization here, there is no lib API
108+
"missing-module-docstring",
109+
# Organization is important, even when just separating classes
110+
"too-few-public-methods",
111+
# Disable any type-checking, that's what mypy is for
112+
"no-member",
113+
# False positives
114+
"cyclic-import",
115+
]
103116

104117
[build-system]
105118
requires = ["poetry>=1.0"]

tests/test___init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,15 +519,24 @@ def test__reformat(mocker):
519519
shell=True,
520520
stdout=subprocess.PIPE,
521521
stderr=subprocess.PIPE,
522+
check=True,
522523
),
523524
mocker.call(
524525
"isort .",
525526
cwd=project.project_dir,
526527
shell=True,
527528
stdout=subprocess.PIPE,
528529
stderr=subprocess.PIPE,
530+
check=True,
531+
),
532+
mocker.call(
533+
"black .",
534+
cwd=project.project_dir,
535+
shell=True,
536+
stdout=subprocess.PIPE,
537+
stderr=subprocess.PIPE,
538+
check=True,
529539
),
530-
mocker.call("black .", cwd=project.project_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE),
531540
]
532541
)
533542

tests/test_utils.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,6 @@ def test_no_string_escapes():
7474
assert utils.remove_string_escapes('an "evil" string') == 'an \\"evil\\" string'
7575

7676

77-
def test__fix_keywords():
78-
assert utils.fix_keywords("None") == "None_"
79-
80-
8177
@pytest.mark.parametrize(
8278
"reserved_word, expected",
8379
[
@@ -87,6 +83,7 @@ def test__fix_keywords():
8783
("not_reserved", "not_reserved"),
8884
("type", "type"),
8985
("id", "id"),
86+
("None", "None_"),
9087
],
9188
)
9289
def test__fix_reserved_words(reserved_word: str, expected: str):

0 commit comments

Comments
 (0)