Skip to content
Open
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
1 change: 0 additions & 1 deletion .github/workflows/on_push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ jobs:
strategy:
matrix:
version:
- "3.12"
- "3.13"
- "3.14"
steps:
Expand Down
22 changes: 13 additions & 9 deletions .github/workflows/on_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ jobs:
deploy:
name: "Release: publish"
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v6
with:
Expand Down Expand Up @@ -40,13 +43,6 @@ jobs:
run: |
git config --global user.email "github-actions@github.com"
git config --global user.name "github-actions"
- name: Commit changes
env:
VERSION: ${{ steps.version.outputs.result }}
run: |
git add pyproject.toml
git commit -m "Release ${VERSION}"
git push
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
Expand All @@ -55,8 +51,16 @@ jobs:
run: |
rm -rf dist/* || true
uv build --sdist --wheel --no-sources
# uv publish
uvx twine upload --non-interactive dist/*
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- name: Commit changes
env:
VERSION: ${{ steps.version.outputs.result }}
run: |
git add pyproject.toml
git commit -m "Release ${VERSION}"
git tag ${STUBS_VERSION}
git push --all --follow-tags
- name: Dockerize
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.13
3.13
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ Compatible with
[Emacs](https://www.gnu.org/software/emacs/),
[Sublime Text](https://www.sublimetext.com/),
[mypy](https://github.com/python/mypy),
[pyright](https://github.com/microsoft/pyright)
[pyright](https://github.com/microsoft/pyright),
[ty](https://docs.astral.sh/ty/),
and other tools.

See how it helps to find and fix potential bugs:
Expand Down
33 changes: 33 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
version: '3'

vars:
RUNNER: uv run
SOURCES: mypy_boto3_builder scripts tests
SOURCES_MAIN: mypy_boto3_builder

tasks:
lint:
cmds:
- "{{.RUNNER}} ruff check {{.SOURCES}}"
format:
cmds:
- "{{.RUNNER}} ruff check --fix {{.SOURCES}}"
- "{{.RUNNER}} ruff format {{.SOURCES}}"
ty:
cmds:
- uvx ty check {{.SOURCES}}
mypy:
cmds:
- "{{.RUNNER}} mypy {{.SOURCES_MAIN}}"
test:
cmds:
- "{{.RUNNER}} pytest"
e2e:
cmds:
- "{{.RUNNER}} python -m scripts.integration"
vulture:
cmds:
- "uvx vulture"
vulture-u:
cmds:
- "uvx vulture {{.SOURCES_MAIN}} --make-whitelist > vulture_whitelist.txt || true"
2 changes: 1 addition & 1 deletion docsmd/thank_you.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[isort](https://github.com/PyCQA/isort) and how flexible it is
- [mypy](https://github.com/python/mypy) developers for doing all dirty work for us
- [pyright](https://github.com/microsoft/pyright) team for the new era of typed Python
- [ruff](https://github.com/astral-sh/ruff) developers for the fastest swiss knife for Python
- [Astral](https://astral.sh/) team for [ruff](https://github.com/astral-sh/ruff), [ty](https://docs.astral.sh/ty/), and other Python tools

## Contributors

Expand Down
3 changes: 2 additions & 1 deletion mypy_boto3_builder/boto3_ports/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Copyright 2024 Vlad Emelianov
"""

from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any

Expand Down Expand Up @@ -161,7 +162,7 @@ class ResourceModel:
"""

def __init__(
self, name: str, definition: dict[str, Any], resource_defs: dict[str, Any]
self, name: str, definition: Mapping[str, Any], resource_defs: dict[str, Any]
) -> None:
self._definition = definition
self._resource_defs = resource_defs
Expand Down
14 changes: 8 additions & 6 deletions mypy_boto3_builder/cli_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def __init__(
default: Sequence[enum.Enum] | None = None,
nargs: str | None = None,
required: bool = False,
deprecated: bool = False,
**kwargs: str | None,
) -> None:
self._enum_type = type
Expand All @@ -70,24 +71,25 @@ def __init__(
type=None,
help=help_str,
required=required,
deprecated=deprecated,
**kwargs,
)

def __call__(
self,
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
value: str | Sequence[Any] | None,
_option_string: str | None = None,
values: str | Sequence[Any] | None,
option_string: str | None = None,
) -> None:
"""
Convert value back into an Enum.
"""
value_list: list[str] = []
if isinstance(value, str):
value_list.append(value)
if isinstance(value, list):
value_list.extend([i for i in value if isinstance(i, str)])
if isinstance(values, str):
value_list.append(values)
if isinstance(values, list):
value_list.extend([i for i in values if isinstance(i, str)])
enum_values = [self._enum_type(i) for i in value_list]
result = enum_values if self.nargs != "?" else enum_values[0]
setattr(namespace, self.dest, result)
Expand Down
13 changes: 10 additions & 3 deletions mypy_boto3_builder/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
import functools
import logging
import sys
from typing import TYPE_CHECKING

import loguru

from mypy_boto3_builder.constants import LOGGER_NAME

if TYPE_CHECKING:
from loguru import BasicHandlerConfig

__all__ = ("get_logger", "setup_logger")


Expand All @@ -38,10 +42,13 @@ def setup_logger(level: int = logging.DEBUG, name: str = LOGGER_NAME) -> None:
Set up logger.
"""
level_name = logging.getLevelName(level)
handler_config: BasicHandlerConfig = {
"sink": sys.stderr,
"level": level_name,
"format": functools.partial(_formatter, name),
}
loguru.logger.configure(
handlers=[
{"sink": sys.stderr, "level": level_name, "format": functools.partial(_formatter, name)}
],
handlers=[handler_config],
)


Expand Down
23 changes: 13 additions & 10 deletions mypy_boto3_builder/parsers/shape_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def get_service_resource_model(self) -> ResourceModel:
raise ShapeParserError("Service resource does not exist")
return ResourceModel(
name=self.service_name.boto3_name,
definition=dict(self._get_service_resource()),
definition=self._get_service_resource(),
resource_defs=self._get_subresources(),
)

Expand All @@ -181,7 +181,7 @@ def get_resource_model(self, resource_name: str) -> ResourceModel:
raise ShapeParserError(f"Resource {resource_name} not found in subresources")
return ResourceModel(
name=resource_name,
definition=dict(resource_defs[resource_name]),
definition=resource_defs[resource_name],
resource_defs=resource_defs,
)

Expand Down Expand Up @@ -1087,17 +1087,18 @@ def _get_resource_method(self, action_name: str, action_shape: ActionShape) -> M
if path.endswith("[]"):
return_type = TypeSubscript(Type.List, [return_type])

operation_model = None
operation_model: OperationModel | None = None
if "request" in action_shape:
operation_name = action_shape["request"]["operation"]
operation_model = self._get_operation(operation_name)
request_operation_model = self._get_operation(operation_name)
operation_model = request_operation_model
Comment on lines +1093 to +1094
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable request_operation_model is immediately assigned to operation_model without any intermediate processing. This introduces unnecessary duplication. Consider directly assigning to operation_model instead.

Copilot uses AI. Check for mistakes.
skip_argument_names = self._get_skip_argument_names(action_shape)
if operation_model.input_shape is not None:
if request_operation_model.input_shape is not None:
shape_arguments = self._parse_arguments(
class_name=self.resource_name,
method_name=method_name,
operation_name=operation_name,
shape=operation_model.input_shape,
shape=request_operation_model.input_shape,
exclude_names=skip_argument_names,
)
arguments.extend(self._get_kw_flags(method_name, shape_arguments))
Expand All @@ -1106,9 +1107,9 @@ def _get_resource_method(self, action_name: str, action_shape: ActionShape) -> M
self._enrich_arguments_defaults(arguments, action_shape)
arguments.sort(key=lambda x: not x.required)

if operation_model.output_shape is not None and return_type is Type.none:
if request_operation_model.output_shape is not None and return_type is Type.none:
operation_return_type = self.parse_shape(
operation_model.output_shape,
request_operation_model.output_shape,
is_output=True,
)
return_type = operation_return_type
Expand All @@ -1118,10 +1119,12 @@ def _get_resource_method(self, action_name: str, action_shape: ActionShape) -> M
arguments=arguments,
return_type=return_type,
docstring=get_short_docstring(
extract_docstring_from_html(operation_model.documentation),
extract_docstring_from_html(
operation_model.documentation if operation_model else ""
),
),
)
if operation_model.input_shape is not None:
if operation_model and operation_model.input_shape is not None:
self.create_request_type_annotation(
method=method,
name=operation_model.input_shape.name,
Expand Down
1 change: 1 addition & 0 deletions mypy_boto3_builder/templates/common/footer.md.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Builder changelog can be found in [Releases]({{ builder_repo_url }}/releases).
[isort](https://github.com/PyCQA/isort) and how flexible it is
- [mypy](https://github.com/python/mypy) developers for doing all dirty work for us
- [pyright](https://github.com/microsoft/pyright) team for the new era of typed Python
- [Astral](https://astral.sh/) team for [ruff](https://github.com/astral-sh/ruff), [ty](https://docs.astral.sh/ty/), and other Python tools

## Documentation

Expand Down
3 changes: 2 additions & 1 deletion mypy_boto3_builder/templates/common/header.md.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ compatible with
[Emacs](https://www.gnu.org/software/emacs/),
[Sublime Text](https://www.sublimetext.com/),
[mypy](https://github.com/python/mypy),
[pyright](https://github.com/microsoft/pyright)
[pyright](https://github.com/microsoft/pyright),
[ty](https://docs.astral.sh/ty/),
and other tools.

Generated with [{{ builder_package_name }} {{ builder_version }}]({{ builder_repo_url }}).
Expand Down
3 changes: 2 additions & 1 deletion mypy_boto3_builder/templates/common/usage.md.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ it is recommended to use [{{ package.data.pypi_lite_name }}]({{ package.url.stub
until the issue is resolved.

> ⚠️ If you experience slow performance and high CPU usage, try to disable `PyCharm` type checker and use
[mypy](https://github.com/python/mypy) or [pyright](https://github.com/microsoft/pyright) instead.
[mypy](https://github.com/python/mypy), [pyright](https://github.com/microsoft/pyright),
or [ty](https://docs.astral.sh/ty/) instead.

> ⚠️ To continue using `PyCharm` type checker, you can try to replace `{{ package.data.pypi_name }}` with
[{{ package.data.pypi_lite_name }}]({{ package.url.stubs_lite_pypi }}):
Expand Down
3 changes: 2 additions & 1 deletion mypy_boto3_builder/templates/common/usage_full.md.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ it is recommended to use [{{ package.data.pypi_lite_name }}]({{ package.url.stub
until the issue is resolved.

> ⚠️ If you experience slow performance and high CPU usage, try to disable `PyCharm` type checker and use
[mypy](https://github.com/python/mypy) or [pyright](https://github.com/microsoft/pyright) instead.
[mypy](https://github.com/python/mypy), [pyright](https://github.com/microsoft/pyright),
or [ty](https://docs.astral.sh/ty/) instead.

> ⚠️ To continue using `PyCharm` type checker, you can try to replace `{{ package.data.pypi_stubs_name }}` with
[{{ package.data.pypi_lite_name }}]({{ package.url.stubs_lite_pypi }}):
Expand Down
18 changes: 9 additions & 9 deletions mypy_boto3_builder/utils/type_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Copyright 2024 Vlad Emelianov
"""

from typing import TypeGuard
from typing import TypeIs

from mypy_boto3_builder.type_annotations.external_import import ExternalImport
from mypy_boto3_builder.type_annotations.fake_annotation import FakeAnnotation
Expand All @@ -18,28 +18,28 @@
from mypy_boto3_builder.type_annotations.type_union import TypeUnion


def is_typed_dict(annotation: FakeAnnotation) -> TypeGuard[TypeTypedDict]:
def is_typed_dict(annotation: FakeAnnotation) -> TypeIs[TypeTypedDict]:
"""
Whether type annotation is TypedDict.
"""
return isinstance(annotation, TypeTypedDict)


def is_union(annotation: FakeAnnotation) -> TypeGuard[TypeUnion]:
def is_union(annotation: FakeAnnotation) -> TypeIs[TypeUnion]:
"""
Whether type annotation is a Union.
"""
return isinstance(annotation, TypeUnion)


def is_literal(annotation: FakeAnnotation) -> TypeGuard[TypeLiteral]:
def is_literal(annotation: FakeAnnotation) -> TypeIs[TypeLiteral]:
"""
Whether type annotation is a literal.
"""
return isinstance(annotation, TypeLiteral)


def is_type_def(annotation: FakeAnnotation) -> TypeGuard[TypeDefSortable]:
def is_type_def(annotation: FakeAnnotation) -> TypeIs[TypeDefSortable]:
"""
Whether type annotation is a named TypeDefSortable.
"""
Expand All @@ -52,28 +52,28 @@ def is_type_def(annotation: FakeAnnotation) -> TypeGuard[TypeDefSortable]:
return False


def is_type_parent(annotation: FakeAnnotation) -> TypeGuard[TypeParent]:
def is_type_parent(annotation: FakeAnnotation) -> TypeIs[TypeParent]:
"""
Whether type annotation is a TypeParent.
"""
return isinstance(annotation, TypeParent)


def is_external_import(annotation: FakeAnnotation) -> TypeGuard[ExternalImport]:
def is_external_import(annotation: FakeAnnotation) -> TypeIs[ExternalImport]:
"""
Whether type annotation is a ExternalImport.
"""
return isinstance(annotation, ExternalImport)


def is_internal_import(annotation: FakeAnnotation) -> TypeGuard[InternalImport]:
def is_internal_import(annotation: FakeAnnotation) -> TypeIs[InternalImport]:
"""
Whether type annotation is a InternalImport.
"""
return isinstance(annotation, InternalImport)


def is_type_subscript(annotation: FakeAnnotation) -> TypeGuard[TypeSubscript]:
def is_type_subscript(annotation: FakeAnnotation) -> TypeIs[TypeSubscript]:
"""
Whether type annotation is a TypeSubscript.
"""
Expand Down
Loading