Skip to content
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

Add support for PyRight #922

Merged
merged 26 commits into from
Sep 10, 2021
Merged
Show file tree
Hide file tree
Changes from 18 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
5 changes: 4 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
architecture: x64

- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install -g --no-package-lock --no-save pyright
- run: pip install poetry
- name: Install dependencies
run: poetry install
Expand Down
4 changes: 4 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Release type: minor

This release adds support for Pyright and Pylance, improving the
integration with Visual Studio Code!
2 changes: 2 additions & 0 deletions TWEET.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Strawberry $version is out! This time with improved support for Pyright and Pylance,
@code's default language server for Python!
4 changes: 4 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
- [Resolvers](./types/resolvers.md)
- [Union types](./types/union.md)

## Editor integration
patrick91 marked this conversation as resolved.
Show resolved Hide resolved

- [Visual Studio Code](./editors/vscode.md)

## Guides

- [Authentication](./guides/authentication.md)
Expand Down
Binary file added docs/editors/pylance.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions docs/editors/vscode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
title: Visual studio code
---

# Visual studio code

Strawberry comes with support for both MyPy and Pylance, Microsoft's own
language server for Python.

This guide will illustrate how to configure Visual Studio Code and Pylance to
patrick91 marked this conversation as resolved.
Show resolved Hide resolved
work with Strawberry.

## Install Pylance

The first thing we need to do is to install
[Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance),
this is the extension that enables type checking and intellisense for Visual
Studio Code.

Once the extension is installed, we need to configure it to enable type
checking. To do so we need to change or add the following two settings:

```json
{
"python.languageServer": "Pylance",
"python.analysis.typeCheckingMode": "basic"
}
```

The first settings tells the editor to use Pylance as the language server. The
second setting tells the editor to enable type checking by using the basic type
checking mode. At the moment strict mode is not supported.

Once you have configured the settings, you can restart VS Code and you should be
getting type checking errors in vscode.

![Pylance showing a type error](./pylance.png)

## Notes

Unfortunately Pylance doesn't fully support `strawberry.field`, so for example
using this code will result in an error from Pylance:

```python
@strawberry.type
class User:
id: int
name: str = strawberry.field(resolver=a)
```

To prevent pylance from erroring you can provide `init=False` to the field
patrick91 marked this conversation as resolved.
Show resolved Hide resolved
function, this won't change any behavior in the code but will prevent pylance
from erroring, like so:

```python
@strawberry.type
class User:
id: int
name: str = strawberry.field(resolver=a, init=False)
```
15 changes: 12 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ classifiers = [
]
include = ["strawberry/py.typed"]

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

[tool.poetry.dependencies]
python = "^3.7"
starlette = {version = ">=0.13.6,<0.17.0", optional = true}
Expand Down Expand Up @@ -112,12 +116,17 @@ sections = ["FUTURE", "STDLIB", "PYTEST", "THIRDPARTY", "DJANGO", "GRAPHQL", "FI
addopts = "-s --emoji --mypy-ini-file=mypy_tests.ini --benchmark-disable"
DJANGO_SETTINGS_MODULE = "tests.django.django_settings"
testpaths = ["tests/"]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

[tool.autopub]
git-username = "Botberry"
git-email = "bot@strawberry.rocks"
project-name = "🍓"
append-github-contributor = true

[tool.pyright]
include = ["strawberry"]
exclude = ["**/__pycache__",]
reportMissingImports = true
reportMissingTypeStubs = false
pythonVersion = "3.7"
stubPath = ""
93 changes: 86 additions & 7 deletions strawberry/federation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import Callable, List, Optional, Type, Union, cast
from typing import Any, Callable, List, Optional, Type, TypeVar, Union, cast, overload

from typing_extensions import Literal

from graphql import (
GraphQLField,
Expand All @@ -11,6 +13,7 @@
)
from graphql.type.definition import GraphQLArgument

from strawberry.arguments import UNSET
from strawberry.custom_scalar import ScalarDefinition
from strawberry.enum import EnumDefinition
from strawberry.permission import BasePermission
Expand All @@ -19,19 +22,27 @@
from strawberry.union import StrawberryUnion
from strawberry.utils.inspect import get_func_args

from .field import FederationFieldParams, field as base_field
from .field import (
_RESOLVER_TYPE,
FederationFieldParams,
StrawberryField,
field as base_field,
)
from .object_type import FederationTypeParams, type as base_type
from .printer import print_schema
from .schema import Schema as BaseSchema


T = TypeVar("T")


def type(
cls: Type = None,
*,
name: str = None,
description: str = None,
keys: List[str] = None,
extend: bool = False
extend: bool = False,
):
return base_type(
cls,
Expand All @@ -41,23 +52,91 @@ def type(
)


@overload
def field(
resolver: Optional[Callable] = None,
*,
resolver: Callable[[], T],
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
provides: Optional[List[str]] = None,
requires: Optional[List[str]] = None,
external: bool = False,
init: Optional[Literal[False]] = False,
permission_classes: Optional[List[Type[BasePermission]]] = None,
deprecation_reason: Optional[str] = None,
default: Any = UNSET,
default_factory: Union[Callable, object] = UNSET,
) -> T:
...


@overload
def field(
*,
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
permission_classes: Optional[List[Type[BasePermission]]] = None
):
return base_field(
provides: Optional[List[str]] = None,
requires: Optional[List[str]] = None,
external: bool = False,
permission_classes: Optional[List[Type[BasePermission]]] = None,
deprecation_reason: Optional[str] = None,
default: Any = UNSET,
default_factory: Union[Callable, object] = UNSET,
) -> Any:
...


@overload
def field(
resolver: _RESOLVER_TYPE,
*,
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
provides: Optional[List[str]] = None,
requires: Optional[List[str]] = None,
external: bool = False,
permission_classes: Optional[List[Type[BasePermission]]] = None,
deprecation_reason: Optional[str] = None,
default: Any = UNSET,
default_factory: Union[Callable, object] = UNSET,
) -> StrawberryField:
...


def field(
resolver: Optional[_RESOLVER_TYPE] = None,
*,
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
provides: Optional[List[str]] = None,
requires: Optional[List[str]] = None,
external: bool = False,
init: Optional[bool] = False,
permission_classes: Optional[List[Type[BasePermission]]] = None,
deprecation_reason: Optional[str] = None,
default: Any = UNSET,
default_factory: Union[Callable, object] = UNSET,
) -> Any:
# hack to prevent mypy from complaining about:
# `Not all union combinations were tried because there are too many unions`
# without using # type: ignore on multiple lines
def _inner(**kwargs):
return base_field(**kwargs)

return _inner(
resolver=resolver,
name=name,
is_subscription=is_subscription,
description=description,
permission_classes=permission_classes,
deprecation_reason=deprecation_reason,
default=default,
default_factory=default_factory,
init=init,
federation=FederationFieldParams(
provides=provides or [], requires=requires or [], external=external
),
Expand Down
57 changes: 56 additions & 1 deletion strawberry/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
Type,
TypeVar,
Union,
overload,
)

from cached_property import cached_property # type: ignore
from typing_extensions import Literal

from strawberry.annotation import StrawberryAnnotation
from strawberry.arguments import UNSET, StrawberryArgument
Expand Down Expand Up @@ -267,8 +269,44 @@ def is_async(self) -> bool:
return self._has_async_permission_classes or self._has_async_base_resolver


T = TypeVar("T")


@overload
def field(
resolver: Optional[_RESOLVER_TYPE] = None,
*,
resolver: Callable[[], T],
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
init: Optional[Literal[False]] = False,
permission_classes: Optional[List[Type[BasePermission]]] = None,
federation: Optional[FederationFieldParams] = None,
deprecation_reason: Optional[str] = None,
default: Any = UNSET,
default_factory: Union[Callable, object] = UNSET,
) -> T:
...


@overload
def field(
*,
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
permission_classes: Optional[List[Type[BasePermission]]] = None,
federation: Optional[FederationFieldParams] = None,
deprecation_reason: Optional[str] = None,
default: Any = UNSET,
default_factory: Union[Callable, object] = UNSET,
) -> Any:
...


@overload
def field(
resolver: _RESOLVER_TYPE,
*,
name: Optional[str] = None,
is_subscription: bool = False,
Expand All @@ -279,6 +317,22 @@ def field(
default: Any = UNSET,
default_factory: Union[Callable, object] = UNSET,
) -> StrawberryField:
...


def field(
resolver: Optional[_RESOLVER_TYPE] = None,
*,
name: Optional[str] = None,
is_subscription: bool = False,
description: Optional[str] = None,
init: Optional[bool] = False,
patrick91 marked this conversation as resolved.
Show resolved Hide resolved
permission_classes: Optional[List[Type[BasePermission]]] = None,
federation: Optional[FederationFieldParams] = None,
deprecation_reason: Optional[str] = None,
default: Any = UNSET,
default_factory: Union[Callable, object] = UNSET,
) -> Any:
"""Annotates a method or property as a GraphQL field.

This is normally used inside a type declaration:
Expand Down Expand Up @@ -308,6 +362,7 @@ def field(
)

if resolver:
assert init is False, "Can't set init as True when passing a resolver."
return field_(resolver)
return field_

Expand Down
Loading