Skip to content

Commit

Permalink
Add initial tests for exports and run them in mypy and pyright (#1135)
Browse files Browse the repository at this point in the history
  • Loading branch information
pakrym-stripe authored Nov 16, 2023
1 parent 8607f1e commit e22152c
Show file tree
Hide file tree
Showing 17 changed files with 151 additions and 22 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ on:

jobs:
lint:
name: Lint
name: Lint & Mypy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: mypy
run: make mypy
- name: lint
run: make lint
- name: fmtcheck
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ $(VENV_NAME)/bin/activate: setup.py requirements.txt
${VENV_NAME}/bin/python -m pip install -r requirements.txt
@touch $(VENV_NAME)/bin/activate

test: venv pyright lint
test: venv pyright lint mypy
@${VENV_NAME}/bin/tox -p auto -e $(DEFAULT_TEST_ENV) $(TOX_ARGS)

test-nomock: venv
Expand All @@ -25,6 +25,9 @@ coveralls: venv
pyright: venv
@${VENV_NAME}/bin/tox -e pyright $(PYRIGHT_ARGS)

mypy: venv
@${VENV_NAME}/bin/tox -e mypy $(MYPY_ARGS)

fmt: venv
@${VENV_NAME}/bin/tox -e fmt

Expand All @@ -43,4 +46,4 @@ update-version:

codegen-format: fmt

.PHONY: ci-test clean codegen-format coveralls fmt fmtcheck lint test test-nomock test-travis update-version venv pyright
.PHONY: ci-test clean codegen-format coveralls fmt fmtcheck lint test test-nomock test-travis update-version venv pyright mypy
16 changes: 15 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,24 @@ exclude = '''
)
'''
[tool.pyright]
include = ["stripe", "tests/test_generated_examples.py"]
include = [
"stripe",
"tests/test_generated_examples.py",
"tests/test_exports.py",
]
exclude = ["build", "**/__pycache__"]
reportMissingTypeArgument = true
reportUnnecessaryCast = true
reportUnnecessaryComparison = true
reportUnnecessaryContains = true
reportUnnecessaryIsInstance = true
reportPrivateImportUsage = true
reportUnnecessaryTypeIgnoreComment = true

[tool.mypy]
follow_imports = "silent"
python_version = "3.10"
files = ["tests/test_exports.py"]
disallow_untyped_calls = true
disallow_untyped_defs = true
warn_unused_ignores = true
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ virtualenv<20.22.0
pyright == 1.1.336
black == 22.8.0
flake8
mypy == 1.7.0

-r test-requirements.txt
4 changes: 2 additions & 2 deletions stripe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
log: Optional[Literal["debug", "info"]] = None

# API resources
from stripe.api_resources import * # pyright: ignore # noqa
from stripe.api_resources import * # noqa

from stripe.api_resources import abstract # pyright: ignore # noqa
from stripe.api_resources import abstract # noqa

# OAuth
from stripe.oauth import OAuth # noqa
Expand Down
2 changes: 1 addition & 1 deletion stripe/api_requestor.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def __init__(
self._client = client
elif stripe.default_http_client:
self._client = stripe.default_http_client
if proxy != self._default_proxy: # type: ignore
if proxy != self._default_proxy:
warnings.warn(
"stripe.proxy was updated after sending a "
"request - this is a no-op. To use a different proxy, "
Expand Down
4 changes: 2 additions & 2 deletions stripe/api_resources/abstract/api_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def _static_request(

if idempotency_key is not None:
headers = {} if headers is None else headers.copy()
headers.update(util.populate_headers(idempotency_key)) # type: ignore
headers.update(util.populate_headers(idempotency_key))

response, api_key = requestor.request(method_, url_, params, headers)
return util.convert_to_stripe_object(
Expand Down Expand Up @@ -186,7 +186,7 @@ def _static_request_stream(

if idempotency_key is not None:
headers = {} if headers is None else headers.copy()
headers.update(util.populate_headers(idempotency_key)) # type: ignore
headers.update(util.populate_headers(idempotency_key))

response, _ = requestor.request_stream(method_, url_, params, headers)
return response
2 changes: 1 addition & 1 deletion stripe/api_resources/abstract/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def class_url(cls):
)
# Namespaces are separated in object names with periods (.) and in URLs
# with forward slashes (/), so replace the former with the latter.
base = cls._resource_cls.OBJECT_NAME.replace(".", "/") # type: ignore
base = cls._resource_cls.OBJECT_NAME.replace(".", "/")
return "/v1/test_helpers/%ss" % (base,)

def instance_url(self):
Expand Down
12 changes: 8 additions & 4 deletions stripe/api_resources/list_object.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# pyright: strict
# pyright: strict, reportUnnecessaryTypeIgnoreComment=false
# reportUnnecessaryTypeIgnoreComment is set to false because some type ignores are required in some
# python versions but not the others
from typing_extensions import Self

from typing import (
Expand Down Expand Up @@ -115,7 +117,9 @@ def __getitem__(self, k: str) -> T:
# Pyright doesn't like this because ListObject inherits from StripeObject inherits from Dict[str, Any]
# and so it wants the type of __iter__ to agree with __iter__ from Dict[str, Any]
# But we are iterating through "data", which is a List[T].
def __iter__(self) -> Iterator[T]: # pyright: ignore
def __iter__( # pyright: ignore
self,
) -> Iterator[T]:
return getattr(self, "data", []).__iter__()

def __len__(self) -> int:
Expand All @@ -132,11 +136,11 @@ def auto_paging_iter(self) -> Iterator[T]:
"ending_before" in self._retrieve_params
and "starting_after" not in self._retrieve_params
):
for item in reversed(page): # type: ignore
for item in reversed(page):
yield item
page = page.previous_page()
else:
for item in page: # type: ignore
for item in page:
yield item
page = page.next_page()

Expand Down
2 changes: 1 addition & 1 deletion stripe/api_resources/quote.py
Original file line number Diff line number Diff line change
Expand Up @@ -1567,7 +1567,7 @@ def pdf(
...

@util.class_method_variant("_cls_pdf")
def pdf( # type: ignore
def pdf( # pyright: ignore
self,
api_key=None,
api_version=None,
Expand Down
2 changes: 1 addition & 1 deletion stripe/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def __repr__(self):
% (
self.__class__.__name__,
self._message,
self.param, # type: ignore
self.param, # pyright: ignore
self.code,
self.http_status,
self.request_id,
Expand Down
4 changes: 2 additions & 2 deletions stripe/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
pycurl = None

try:
import requests # pyright: ignore
import requests
except ImportError:
requests = None
else:
Expand Down Expand Up @@ -61,7 +61,7 @@
requests = None

try:
from google.appengine.api import urlfetch # type: ignore
from google.appengine.api import urlfetch # pyright: ignore
except ImportError:
urlfetch = None

Expand Down
5 changes: 4 additions & 1 deletion stripe/multipart_data_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ def __init__(self, chunk_size: int = 1028):

def add_params(self, params):
# Flatten parameters first
params = dict(stripe.api_requestor._api_encode(params)) # type: ignore

params = dict(
stripe.api_requestor._api_encode(params) # pyright: ignore
)

for key, value in params.items():
if value is None:
Expand Down
2 changes: 1 addition & 1 deletion stripe/oauth_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def construct_error_object(self):
if self.json_body is None:
return None

return stripe.api_resources.error_object.OAuthErrorObject.construct_from( # type: ignore
return stripe.api_resources.error_object.OAuthErrorObject.construct_from( # pyright: ignore
self.json_body, stripe.api_key
)

Expand Down
4 changes: 3 additions & 1 deletion stripe/stripe_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ def last_response(self) -> Optional[StripeResponse]:

# StripeObject inherits from `dict` which has an update method, and this doesn't quite match
# the full signature of the update method in MutableMapping. But we ignore.
def update(self, update_dict: Mapping[str, Any]) -> None: # type: ignore[override]
def update( # pyright: ignore
self, update_dict: Mapping[str, Any]
) -> None:
for k in update_dict:
self._unsaved_values.add(k)

Expand Down
98 changes: 98 additions & 0 deletions tests/test_exports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# pyright: strict
import stripe


def test_can_import_stripe_object() -> None:
from stripe.stripe_object import (
StripeObject as StripeObjectFromStripeStripeObject,
)

assert (
stripe.stripe_object.StripeObject is StripeObjectFromStripeStripeObject
)


def test_can_import_webhook_members() -> None:
from stripe import (
Webhook,
WebhookSignature,
)

assert Webhook is not None
assert WebhookSignature is not None


def test_can_import_abstract() -> None:
from stripe.api_resources.abstract import (
APIResource as APIResourceFromApiResourcesAbstract,
)
from stripe.stripe_object import (
StripeObject,
)

assert (
APIResourceFromApiResourcesAbstract[StripeObject]
== stripe.abstract.APIResource[StripeObject]
)


def test_can_import_app_info() -> None:
from stripe.app_info import AppInfo as AppInfoFromStripeAppInfo
from stripe import AppInfo as AppInfoFromStripe

assert AppInfoFromStripeAppInfo is AppInfoFromStripe
assert AppInfoFromStripeAppInfo is stripe.AppInfo


def test_can_import_stripe_response() -> None:
from stripe.stripe_response import (
StripeResponse as StripeResponseFromStripeResponse,
)

assert (
StripeResponseFromStripeResponse
is stripe.stripe_response.StripeResponse
)


def test_can_import_oauth_members() -> None:
from stripe import (
OAuth,
)

assert OAuth is not None


def test_can_import_errors() -> None:
from stripe.error import (
StripeError as StripeErrorFromStripeError,
)

assert StripeErrorFromStripeError is not None


def test_can_import_top_level_resource() -> None:
from stripe import Account as AccountFromStripe
from stripe.api_resources import Account as AccountFromStripeResources
from stripe.api_resources.account import (
Account as AccountFromStripeResourcesAccount,
)

assert stripe.Account == AccountFromStripe
assert AccountFromStripe == AccountFromStripeResources
assert AccountFromStripeResourcesAccount == AccountFromStripeResources


def test_can_import_namespaced_resource() -> None:
from stripe import tax as TaxPackage
from stripe.api_resources.tax import (
Calculation as CalculationFromResources,
)
from stripe.api_resources.tax.calculation import (
Calculation as CalculationFromResourcesCalculation,
)

assert stripe.tax is TaxPackage
assert stripe.tax.Calculation is TaxPackage.Calculation
assert stripe.tax.Calculation is CalculationFromResources
assert CalculationFromResources is CalculationFromResourcesCalculation
4 changes: 3 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ envlist =
fmt
lint
pyright
mypy
py{312,311,310,39,38,37,36,py3}
ignore_base_python_conflict = false

Expand All @@ -32,13 +33,14 @@ commands = pytest --cov {posargs:-n auto} --ignore stripe
# CFLAGS="-I$(brew --prefix openssl@1.1)/include"
passenv = LDFLAGS,CFLAGS

[testenv:{lint,fmt,pyright}]
[testenv:{lint,fmt,pyright,mypy}]
basepython = python3.10
skip_install = true
commands =
pyright: pyright {posargs}
lint: python -m flake8 --show-source stripe tests setup.py
fmt: black . {posargs}
mypy: mypy {posargs}
deps =
-r requirements.txt

Expand Down

0 comments on commit e22152c

Please sign in to comment.