Skip to content

ci: Add Pylint #472

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ jobs:
- name: Run mypy
run: poetry run mypy --show-error-codes openapi_python_client

- name: Run pylint
run: poetry run pylint openapi_python_client

- name: Run pytest
run: poetry run pytest --cov=openapi_python_client --cov-report=term-missing tests end_to_end_tests/test_end_to_end.py

Expand Down
14 changes: 11 additions & 3 deletions openapi_python_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@


class MetaType(str, Enum):
"""The types of metadata supported for project generation."""

NONE = "none"
POETRY = "poetry"
SETUP = "setup"
Expand All @@ -41,7 +43,9 @@ class MetaType(str, Enum):
}


class Project:
class Project: # pylint: disable=too-many-instance-attributes
"""Represents a Python project (the top level file-tree) to generate"""

def __init__(
self,
*,
Expand Down Expand Up @@ -129,15 +133,19 @@ def _reformat(self) -> None:
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
subprocess.run(
"isort .",
cwd=self.project_dir,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
subprocess.run(
"black .", cwd=self.project_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True
)
subprocess.run("black .", cwd=self.project_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

def _get_errors(self) -> Sequence[GeneratorError]:
errors = []
Expand Down Expand Up @@ -263,7 +271,7 @@ def _build_api(self) -> None:
module_path.write_text(endpoint_template.render(endpoint=endpoint), encoding=self.file_encoding)


def _get_project_for_url_or_path(
def _get_project_for_url_or_path( # pylint: disable=too-many-arguments
url: Optional[str],
path: Optional[Path],
meta: MetaType,
Expand Down
28 changes: 15 additions & 13 deletions openapi_python_client/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,28 @@ def _process_config(path: Optional[pathlib.Path]) -> Config:

try:
return Config.load_from_path(path=path)
except: # noqa
raise typer.BadParameter("Unable to parse config")
except Exception as err:
raise typer.BadParameter("Unable to parse config") from err


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


def _print_parser_error(e: GeneratorError, color: str) -> None:
typer.secho(e.header, bold=True, fg=color, err=True)
def _print_parser_error(err: GeneratorError, color: str) -> None:
typer.secho(err.header, bold=True, fg=color, err=True)
typer.echo()
if e.detail:
typer.secho(e.detail, fg=color, err=True)
if err.detail:
typer.secho(err.detail, fg=color, err=True)
typer.echo()

if isinstance(e, ParseError) and e.data is not None:
formatted_data = pformat(e.data)
if isinstance(err, ParseError) and err.data is not None:
formatted_data = pformat(err.data)
typer.secho(formatted_data, fg=color, err=True)

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


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

try:
codecs.getencoder(file_encoding)
except LookupError:
except LookupError as err:
typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED)
raise typer.Exit(code=1)
raise typer.Exit(code=1) from err

config = _process_config(config_path)
errors = create_new_client(
Expand All @@ -149,6 +150,7 @@ def generate(
handle_errors(errors, fail_on_warning)


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

try:
codecs.getencoder(file_encoding)
except LookupError:
except LookupError as err:
typer.secho("Unknown encoding : {}".format(file_encoding), fg=typer.colors.RED)
raise typer.Exit(code=1)
raise typer.Exit(code=1) from err

config = _process_config(config_path)
errors = update_existing_client(
Expand Down
10 changes: 10 additions & 0 deletions openapi_python_client/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@


class ClassOverride(BaseModel):
"""An override of a single generated class.

See https://github.com/openapi-generators/openapi-python-client#class_overrides
"""

class_name: Optional[str] = None
module_name: Optional[str] = None


class Config(BaseModel):
"""Contains any configurable values passed by the user.

See https://github.com/openapi-generators/openapi-python-client#configuration
"""

class_overrides: Dict[str, ClassOverride] = {}
project_name_override: Optional[str]
package_name_override: Optional[str]
Expand Down
2 changes: 1 addition & 1 deletion openapi_python_client/parser/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ class PropertyError(ParseError):


class ValidationError(Exception):
pass
"""Used internally to exit quickly from property parsing due to some internal exception."""
52 changes: 40 additions & 12 deletions openapi_python_client/parser/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def generate_operation_id(*, path: str, method: str) -> str:
return f"{method}_{clean_path}"


# pylint: disable=too-many-instance-attributes
@dataclass
class Endpoint:
"""
Expand Down Expand Up @@ -244,10 +245,32 @@ def _add_responses(
endpoint.responses.append(response)
return endpoint, schemas

# pylint: disable=too-many-return-statements
@staticmethod
def add_parameters(
*, endpoint: "Endpoint", data: Union[oai.Operation, oai.PathItem], schemas: Schemas, config: Config
) -> Tuple[Union["Endpoint", ParseError], Schemas]:
"""Process the defined `parameters` for an Endpoint.

Any existing parameters will be ignored, so earlier instances of a parameter take precedence. PathItem
parameters should therefore be added __after__ operation parameters.

Args:
endpoint: The endpoint to add parameters to.
data: The Operation or PathItem to add parameters from.
schemas: The cumulative Schemas of processing so far which should contain details for any references.
config: User-provided config for overrides within parameters.

Returns:
`(result, schemas)` where `result` is either an updated Endpoint containing the parameters or a ParseError
describing what went wrong. `schemas` is an updated version of the `schemas` input, adding any new enums
or classes.

See Also:
- https://swagger.io/docs/specification/describing-parameters/
- https://swagger.io/docs/specification/paths-and-operations/
"""

endpoint = deepcopy(endpoint)
if data.parameters is None:
return endpoint, schemas
Expand Down Expand Up @@ -329,6 +352,16 @@ def add_parameters(

@staticmethod
def sort_parameters(*, endpoint: "Endpoint") -> Union["Endpoint", ParseError]:
"""
Sorts the path parameters of an `endpoint` so that they match the order declared in `endpoint.path`.

Args:
endpoint: The endpoint to sort the parameters of.

Returns:
Either an updated `endpoint` with sorted path parameters or a `ParseError` if something was wrong with
the path parameters and they could not be sorted.
"""
endpoint = deepcopy(endpoint)
parameters_from_path = re.findall(_PATH_PARAM_REGEX, endpoint.path)
try:
Expand All @@ -338,8 +371,8 @@ def sort_parameters(*, endpoint: "Endpoint") -> Union["Endpoint", ParseError]:
endpoint.path_parameters = OrderedDict((param.name, param) for param in sorted_params)
except ValueError:
pass # We're going to catch the difference down below
path_parameter_names = [name for name in endpoint.path_parameters]
if parameters_from_path != path_parameter_names:

if parameters_from_path != list(endpoint.path_parameters):
return ParseError(
detail=f"Incorrect path templating for {endpoint.path} (Path parameters do not match with path)",
)
Expand Down Expand Up @@ -397,22 +430,17 @@ class GeneratorData:
enums: Iterator[EnumProperty]

@staticmethod
def from_dict(d: Dict[str, Any], *, config: Config) -> Union["GeneratorData", GeneratorError]:
def from_dict(data: Dict[str, Any], *, config: Config) -> Union["GeneratorData", GeneratorError]:
"""Create an OpenAPI from dict"""
try:
openapi = oai.OpenAPI.parse_obj(d)
except ValidationError as e:
detail = str(e)
if "swagger" in d:
openapi = oai.OpenAPI.parse_obj(data)
except ValidationError as err:
detail = str(err)
if "swagger" in data:
detail = (
"You may be trying to use a Swagger document; this is not supported by this project.\n\n" + detail
)
return GeneratorError(header="Failed to parse OpenAPI document", detail=detail)
if openapi.openapi.major != 3:
return GeneratorError(
header="openapi-python-client only supports OpenAPI 3.x",
detail=f"The version of the provided document was {openapi.openapi}",
)
schemas = Schemas()
if openapi.components and openapi.components.schemas:
schemas = build_schemas(components=openapi.components.schemas, schemas=schemas, config=config)
Expand Down
Loading