FlaskTester 5.1__tar.gz → 6.0__tar.gz
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.
- {flasktester-5.1 → flasktester-6.0}/FlaskTester.egg-info/PKG-INFO +2 -1
- {flasktester-5.1 → flasktester-6.0}/FlaskTester.egg-info/requires.txt +1 -0
- {flasktester-5.1 → flasktester-6.0}/FlaskTester.py +40 -26
- {flasktester-5.1 → flasktester-6.0}/PKG-INFO +2 -1
- {flasktester-5.1 → flasktester-6.0}/pyproject.toml +6 -2
- {flasktester-5.1 → flasktester-6.0}/FlaskTester.egg-info/SOURCES.txt +0 -0
- {flasktester-5.1 → flasktester-6.0}/FlaskTester.egg-info/dependency_links.txt +0 -0
- {flasktester-5.1 → flasktester-6.0}/FlaskTester.egg-info/top_level.txt +0 -0
- {flasktester-5.1 → flasktester-6.0}/LICENSE +0 -0
- {flasktester-5.1 → flasktester-6.0}/MANIFEST.in +0 -0
- {flasktester-5.1 → flasktester-6.0}/README.md +0 -0
- {flasktester-5.1 → flasktester-6.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: FlaskTester
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0
|
|
4
4
|
Summary: Pytest fixtures for Flask internal and external authenticated tests
|
|
5
5
|
Author-email: Fabien Coelho <flask.tester@coelho.net>
|
|
6
6
|
License-Expression: CC0-1.0
|
|
@@ -25,6 +25,7 @@ Requires-Dist: pyright; extra == "dev"
|
|
|
25
25
|
Requires-Dist: ruff; extra == "dev"
|
|
26
26
|
Requires-Dist: coverage; extra == "dev"
|
|
27
27
|
Requires-Dist: pymarkdownlnt!=0.9.5; extra == "dev"
|
|
28
|
+
Requires-Dist: flake8; extra == "dev"
|
|
28
29
|
Requires-Dist: types-flask; extra == "dev"
|
|
29
30
|
Requires-Dist: types-requests; extra == "dev"
|
|
30
31
|
Requires-Dist: FlaskSimpleAuth>=35.0; extra == "dev"
|
|
@@ -7,7 +7,8 @@ import os
|
|
|
7
7
|
import io
|
|
8
8
|
import re
|
|
9
9
|
# Python >= 3.11: Self
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
11
|
+
from collections.abc import Callable
|
|
11
12
|
import importlib
|
|
12
13
|
import logging
|
|
13
14
|
import pytest # for explicit fail calls, see _pytestFail
|
|
@@ -90,23 +91,26 @@ class Authenticator:
|
|
|
90
91
|
_AUTH_SCHEMES.update(_TOKEN_SCHEMES)
|
|
91
92
|
_AUTH_SCHEMES.update(_PASS_SCHEMES)
|
|
92
93
|
|
|
94
|
+
# should not be mutable
|
|
95
|
+
_AUTH_DEFAULT_SCHEMES = ["bearer", "basic", "param", "none"]
|
|
96
|
+
|
|
93
97
|
# authenticator login/pass hook
|
|
94
98
|
_AuthHook = Callable[[str, str|None], None]
|
|
95
99
|
|
|
96
100
|
def __init__(self,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
allow: list[str] = _AUTH_DEFAULT_SCHEMES,
|
|
102
|
+
# parameter names for "basic" and "param"
|
|
103
|
+
user: str = "USER",
|
|
104
|
+
pwd: str = "PASS",
|
|
105
|
+
# parameter name for "fake"
|
|
106
|
+
login: str = "LOGIN",
|
|
107
|
+
# 4 token options
|
|
108
|
+
bearer: str = "Bearer",
|
|
109
|
+
header: str = "Auth",
|
|
110
|
+
cookie: str = "auth",
|
|
111
|
+
tparam: str = "AUTH",
|
|
112
|
+
ptype: str = "data",
|
|
113
|
+
):
|
|
110
114
|
|
|
111
115
|
self._has_pass, self._has_token = False, False
|
|
112
116
|
for auth in allow:
|
|
@@ -139,8 +143,7 @@ class Authenticator:
|
|
|
139
143
|
def _set(self, key: str, val: str|None, store: dict[str, str]):
|
|
140
144
|
"""Set a key/value in a directory, with None for delete."""
|
|
141
145
|
if val is None:
|
|
142
|
-
|
|
143
|
-
del store[key]
|
|
146
|
+
store.pop(key, None)
|
|
144
147
|
else:
|
|
145
148
|
assert isinstance(val, str)
|
|
146
149
|
store[key] = val
|
|
@@ -315,6 +318,7 @@ class Client:
|
|
|
315
318
|
AuthHook = Callable[[Any, str, str|None], None] # type: ignore
|
|
316
319
|
|
|
317
320
|
def __init__(self, auth: Authenticator, default_login: str|None = None):
|
|
321
|
+
self._calls = 0
|
|
318
322
|
self._auth = auth
|
|
319
323
|
self._cookies: dict[str, dict[str, str]] = {} # login -> name -> value
|
|
320
324
|
self._default_login = default_login
|
|
@@ -357,6 +361,8 @@ class Client:
|
|
|
357
361
|
:param **kwargs: More request parameters (headers, data, json…).
|
|
358
362
|
"""
|
|
359
363
|
|
|
364
|
+
self._calls += 1
|
|
365
|
+
|
|
360
366
|
if "login" in kwargs:
|
|
361
367
|
login = kwargs["login"]
|
|
362
368
|
del kwargs["login"]
|
|
@@ -387,7 +393,7 @@ class Client:
|
|
|
387
393
|
elif "model_dump" in val.__dir__() and callable(val.model_dump):
|
|
388
394
|
# probably pydantic
|
|
389
395
|
json_param[name] = val.model_dump()
|
|
390
|
-
else:
|
|
396
|
+
else: # pydantic or standard dataclasses?
|
|
391
397
|
json_param[name] = dataclasses.asdict(val)
|
|
392
398
|
|
|
393
399
|
# convert data parameters to simple strings
|
|
@@ -415,16 +421,14 @@ class Client:
|
|
|
415
421
|
res = self._request(method, path, cookies, **kwargs) # type: ignore
|
|
416
422
|
|
|
417
423
|
# check status
|
|
418
|
-
if status is not None:
|
|
419
|
-
if
|
|
420
|
-
|
|
421
|
-
_pytestFail(f"bad {status} result: {res.status_code} {res.text[:512]}...")
|
|
424
|
+
if status is not None and res.status_code != status: # show error before aborting
|
|
425
|
+
# FIXME what if the useful part is at the end?
|
|
426
|
+
_pytestFail(f"bad {status} result: {res.status_code} {res.text[:512]}...")
|
|
422
427
|
|
|
423
428
|
# check content
|
|
424
|
-
if content is not None:
|
|
425
|
-
if
|
|
426
|
-
|
|
427
|
-
_pytestFail(f"cannot find {content} in {res.text[:512]}...")
|
|
429
|
+
if content is not None and not re.search(content, res.text, re.DOTALL):
|
|
430
|
+
# FIXME what if the useful part is at the end?
|
|
431
|
+
_pytestFail(f"cannot find {content} in {res.text[:512]}...")
|
|
428
432
|
|
|
429
433
|
return res
|
|
430
434
|
|
|
@@ -448,6 +452,12 @@ class Client:
|
|
|
448
452
|
"""HTTP DELETE request, see `Client.request`."""
|
|
449
453
|
return self.request("DELETE", path, status=status, content=content, **kwargs)
|
|
450
454
|
|
|
455
|
+
def __str__(self):
|
|
456
|
+
return f"client calls: {self._calls}"
|
|
457
|
+
|
|
458
|
+
def __del__(self):
|
|
459
|
+
log.info(f"ending, {self}")
|
|
460
|
+
|
|
451
461
|
|
|
452
462
|
class RequestClient(Client):
|
|
453
463
|
"""Request-based test provider.
|
|
@@ -526,6 +536,7 @@ class FlaskClient(Client):
|
|
|
526
536
|
|
|
527
537
|
return self._client.open(method=method, path=path, **kwargs)
|
|
528
538
|
|
|
539
|
+
|
|
529
540
|
def _ft_authenticator():
|
|
530
541
|
"""Fixture implementation, separated for testing purposes."""
|
|
531
542
|
|
|
@@ -559,6 +570,7 @@ def _ft_authenticator():
|
|
|
559
570
|
|
|
560
571
|
return auth
|
|
561
572
|
|
|
573
|
+
|
|
562
574
|
@pytest.fixture
|
|
563
575
|
def ft_authenticator():
|
|
564
576
|
"""Pytest Fixture: ft_authenticator.
|
|
@@ -593,6 +605,7 @@ def ft_authenticator():
|
|
|
593
605
|
|
|
594
606
|
yield _ft_authenticator()
|
|
595
607
|
|
|
608
|
+
|
|
596
609
|
def _ft_client(authenticator):
|
|
597
610
|
"""Fixture implementation separated for testing."""
|
|
598
611
|
|
|
@@ -601,7 +614,7 @@ def _ft_client(authenticator):
|
|
|
601
614
|
|
|
602
615
|
test_app = os.environ.get("FLASK_TESTER_APP", "app")
|
|
603
616
|
|
|
604
|
-
if test_app.startswith("http://"
|
|
617
|
+
if test_app.startswith(("http://", "https://")):
|
|
605
618
|
client = RequestClient(authenticator, test_app, default_login)
|
|
606
619
|
else:
|
|
607
620
|
# load app package
|
|
@@ -627,6 +640,7 @@ def _ft_client(authenticator):
|
|
|
627
640
|
|
|
628
641
|
return client
|
|
629
642
|
|
|
643
|
+
|
|
630
644
|
@pytest.fixture
|
|
631
645
|
def ft_client(ft_authenticator):
|
|
632
646
|
"""Pytest Fixture: ft_client.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: FlaskTester
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0
|
|
4
4
|
Summary: Pytest fixtures for Flask internal and external authenticated tests
|
|
5
5
|
Author-email: Fabien Coelho <flask.tester@coelho.net>
|
|
6
6
|
License-Expression: CC0-1.0
|
|
@@ -25,6 +25,7 @@ Requires-Dist: pyright; extra == "dev"
|
|
|
25
25
|
Requires-Dist: ruff; extra == "dev"
|
|
26
26
|
Requires-Dist: coverage; extra == "dev"
|
|
27
27
|
Requires-Dist: pymarkdownlnt!=0.9.5; extra == "dev"
|
|
28
|
+
Requires-Dist: flake8; extra == "dev"
|
|
28
29
|
Requires-Dist: types-flask; extra == "dev"
|
|
29
30
|
Requires-Dist: types-requests; extra == "dev"
|
|
30
31
|
Requires-Dist: FlaskSimpleAuth>=35.0; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "FlaskTester"
|
|
7
|
-
version = "
|
|
7
|
+
version = "6.0"
|
|
8
8
|
authors = [ { name = "Fabien Coelho", email = "flask.tester@coelho.net" } ]
|
|
9
9
|
description = "Pytest fixtures for Flask internal and external authenticated tests"
|
|
10
10
|
readme = "README.md"
|
|
@@ -33,7 +33,7 @@ package = "https://pypi.org/project/FlaskTester/"
|
|
|
33
33
|
# various dev tools
|
|
34
34
|
dev = [
|
|
35
35
|
# static checks…
|
|
36
|
-
"mypy", "pyright", "ruff", "coverage", "pymarkdownlnt!=0.9.5",
|
|
36
|
+
"mypy", "pyright", "ruff", "coverage", "pymarkdownlnt!=0.9.5", "flake8",
|
|
37
37
|
# tests
|
|
38
38
|
"types-flask", "types-requests", "FlaskSimpleAuth>=35.0", "bcrypt", "pydantic"
|
|
39
39
|
]
|
|
@@ -47,3 +47,7 @@ py-modules = [ "FlaskTester" ]
|
|
|
47
47
|
[tool.black]
|
|
48
48
|
line-length = 100
|
|
49
49
|
target-version = ["py310"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint]
|
|
52
|
+
# ruff rules are what they are, but they are not always right
|
|
53
|
+
ignore = ["BLE001", "B006", "I001", "SIM114", "RUF012", "PIE790"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|