pytest-graphql 0.1.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. pytest_graphql/__init__.py +62 -0
  2. pytest_graphql/_core/__init__.py +7 -0
  3. pytest_graphql/_core/auth.py +89 -0
  4. pytest_graphql/_core/client.py +921 -0
  5. pytest_graphql/_core/diagnostics.py +2352 -0
  6. pytest_graphql/_core/errors.py +551 -0
  7. pytest_graphql/_core/headers.py +40 -0
  8. pytest_graphql/_core/lifecycle.py +1119 -0
  9. pytest_graphql/_core/middleware.py +73 -0
  10. pytest_graphql/_core/naming.py +107 -0
  11. pytest_graphql/_core/operation.py +464 -0
  12. pytest_graphql/_core/response/__init__.py +23 -0
  13. pytest_graphql/_core/response/envelope.py +158 -0
  14. pytest_graphql/_core/response/materialize.py +418 -0
  15. pytest_graphql/_core/response/node.py +221 -0
  16. pytest_graphql/_core/response/types.py +118 -0
  17. pytest_graphql/_core/schema/__init__.py +3 -0
  18. pytest_graphql/_core/schema/cache.py +32 -0
  19. pytest_graphql/_core/schema/info.py +56 -0
  20. pytest_graphql/_core/schema/source.py +112 -0
  21. pytest_graphql/_core/selection/__init__.py +11 -0
  22. pytest_graphql/_core/selection/builder.py +822 -0
  23. pytest_graphql/_core/selection/model.py +202 -0
  24. pytest_graphql/_core/selection/normalize.py +1423 -0
  25. pytest_graphql/_core/selection/policy.py +261 -0
  26. pytest_graphql/_core/transport/__init__.py +10 -0
  27. pytest_graphql/_core/transport/base.py +79 -0
  28. pytest_graphql/_core/transport/httpx_transport.py +1020 -0
  29. pytest_graphql/_core/validation.py +147 -0
  30. pytest_graphql/plugin/__init__.py +143 -0
  31. pytest_graphql/py.typed +0 -0
  32. pytest_graphql-0.1.0a1.dist-info/METADATA +120 -0
  33. pytest_graphql-0.1.0a1.dist-info/RECORD +36 -0
  34. pytest_graphql-0.1.0a1.dist-info/WHEEL +4 -0
  35. pytest_graphql-0.1.0a1.dist-info/entry_points.txt +2 -0
  36. pytest_graphql-0.1.0a1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,62 @@
1
+ """Schema-aware GraphQL test client with an optional pytest plugin.
2
+
3
+ The public API is re-exported from this module. Per C9, only names a user
4
+ constructs, catches, annotates or calls belong in ``__all__``; a name lands
5
+ here in the milestone that implements it, and the rest of the documented
6
+ surface arrives with the milestones that still owe it. ``__version__`` is the
7
+ single source of the distribution version, and the release workflow checks
8
+ it against the tag.
9
+
10
+ Importing this module imports no pytest (C41). The pytest layer lives under
11
+ ``pytest_graphql.plugin`` and nothing here reaches into it.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from pytest_graphql._core.auth import Auth, BearerAuth, HeaderAuth
17
+ from pytest_graphql._core.client import (
18
+ ClientConfig,
19
+ GraphQLClient,
20
+ build_client,
21
+ )
22
+ from pytest_graphql._core.diagnostics import DiagnosticSnapshot, RequestInfo
23
+ from pytest_graphql._core.errors import (
24
+ DiagnosticRenderError,
25
+ GraphQLFieldError,
26
+ ResponseShapeError,
27
+ )
28
+ from pytest_graphql._core.middleware import BaseMiddleware, Middleware
29
+ from pytest_graphql._core.response import GraphQLResponse, Node, NodeList
30
+ from pytest_graphql._core.schema.source import SchemaSource
31
+ from pytest_graphql._core.selection.model import AUTO, Field, Selection
32
+ from pytest_graphql._core.selection.policy import CyclePolicy, SelectionPolicy
33
+ from pytest_graphql._core.transport.base import Transport
34
+
35
+ __all__ = [
36
+ "AUTO",
37
+ "Auth",
38
+ "BaseMiddleware",
39
+ "BearerAuth",
40
+ "ClientConfig",
41
+ "CyclePolicy",
42
+ "DiagnosticRenderError",
43
+ "DiagnosticSnapshot",
44
+ "Field",
45
+ "GraphQLClient",
46
+ "GraphQLFieldError",
47
+ "GraphQLResponse",
48
+ "HeaderAuth",
49
+ "Middleware",
50
+ "Node",
51
+ "NodeList",
52
+ "RequestInfo",
53
+ "ResponseShapeError",
54
+ "SchemaSource",
55
+ "Selection",
56
+ "SelectionPolicy",
57
+ "Transport",
58
+ "__version__",
59
+ "build_client",
60
+ ]
61
+
62
+ __version__ = "0.1.0a1"
@@ -0,0 +1,7 @@
1
+ """Private implementation package.
2
+
3
+ Nothing here is public API, and nothing here may import pytest. The purity check
4
+ in ``scripts/check_core_purity.py`` enforces the second rule in CI.
5
+ """
6
+
7
+ from __future__ import annotations
@@ -0,0 +1,89 @@
1
+ """The ``Auth`` protocol and the two implementations that ship (B5).
2
+
3
+ ``Auth.apply`` is called once per request, so a token that has to be refreshed
4
+ is refreshed inside ``apply`` and needs no extra machinery. It sits fourth in
5
+ the C4 header precedence chain: above ``ClientConfig.headers`` and the
6
+ ``gql_headers`` fixture, below ``with_headers()`` and a per-call ``headers=``.
7
+
8
+ ``apply`` receives the live ``RequestInfo`` (C2), because an implementation
9
+ genuinely needs the real values to sign or refresh a request. The leak
10
+ boundary is rendering, not the object, so nothing here may render one: this
11
+ module produces a new ``RequestInfo`` and never text.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import dataclasses
17
+ from collections.abc import Mapping
18
+ from typing import Protocol, runtime_checkable
19
+
20
+ from pytest_graphql._core.diagnostics import RequestInfo
21
+ from pytest_graphql._core.headers import merge_headers
22
+
23
+
24
+ @runtime_checkable
25
+ class Auth(Protocol):
26
+ """One request identity (B5)."""
27
+
28
+ def apply(self, request: RequestInfo) -> RequestInfo: ...
29
+
30
+
31
+ def with_headers(request: RequestInfo, headers: Mapping[str, str]) -> RequestInfo:
32
+ """``request`` with ``headers`` merged over its own, under the C4 rule.
33
+
34
+ The one place an ``Auth`` implementation in this package rewrites a
35
+ request, so the case-insensitive replacement rule is applied once rather
36
+ than at each implementation.
37
+ """
38
+ return dataclasses.replace(request, headers=merge_headers(request.headers, headers))
39
+
40
+
41
+ class BearerAuth:
42
+ """``Authorization: Bearer <token>`` (B5).
43
+
44
+ The token is held as given. It reaches no representation: ``RequestInfo``
45
+ renders only its redacted snapshot, and ``authorization`` is in the
46
+ default ``redact_headers`` set, so the rendered form is a placeholder and
47
+ the value enters the C16 secret set for the free-form-text scrub.
48
+ """
49
+
50
+ __slots__ = ("token",)
51
+
52
+ def __init__(self, token: str) -> None:
53
+ self.token = token
54
+
55
+ def apply(self, request: RequestInfo) -> RequestInfo:
56
+ return with_headers(request, {"Authorization": f"Bearer {self.token}"})
57
+
58
+ def __repr__(self) -> str:
59
+ return "BearerAuth(token=<redacted>)"
60
+
61
+
62
+ class HeaderAuth:
63
+ """A fixed set of identity headers (B5).
64
+
65
+ B5 writes the constructor as ``HeaderAuth(**headers)``. A keyword cannot
66
+ spell a name containing a hyphen, and almost every real identity header
67
+ does, so an optional positional mapping is accepted as well, exactly as
68
+ B21 does for ``with_headers()`` and for the same reason:
69
+ ``HeaderAuth({"X-Api-Key": "v"}, Authorization="Bearer x")``.
70
+ """
71
+
72
+ __slots__ = ("headers",)
73
+
74
+ def __init__(
75
+ self, headers: Mapping[str, str] | None = None, /, **named: str
76
+ ) -> None:
77
+ self.headers = merge_headers(headers, named)
78
+
79
+ def apply(self, request: RequestInfo) -> RequestInfo:
80
+ return with_headers(request, self.headers)
81
+
82
+ def __repr__(self) -> str:
83
+ names = ", ".join(sorted(self.headers))
84
+ return f"HeaderAuth(names=[{names}], values=<redacted>)"
85
+
86
+
87
+ def as_(auth: Auth | str) -> Auth:
88
+ """Resolve the documented shorthand: a bare string is a bearer token."""
89
+ return BearerAuth(auth) if isinstance(auth, str) else auth