FlaskTester 4.0__tar.gz → 4.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: FlaskTester
3
- Version: 4.0
3
+ Version: 4.2
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: CC0
@@ -34,6 +34,7 @@ Requires-Dist: types-flask; extra == "dev"
34
34
  Requires-Dist: types-requests; extra == "dev"
35
35
  Requires-Dist: FlaskSimpleAuth>=30.0; extra == "dev"
36
36
  Requires-Dist: passlib; extra == "dev"
37
+ Requires-Dist: pydantic; extra == "dev"
37
38
  Provides-Extra: doc
38
39
  Requires-Dist: sphinx; extra == "doc"
39
40
  Requires-Dist: sphinx_rtd_theme; extra == "doc"
@@ -123,7 +124,7 @@ The above test runs with [`tests/app.py`](tests/app.py)
123
124
  [Flask](https://flask.palletsprojects.com/)
124
125
  REST application back-end with password and token authentication based on
125
126
  [FlaskSimpleAuth](https://pypi.org/project/FlaskSimpleAuth/).
126
- The code uses _25_ lines of Python for implementing
127
+ The code uses _23_ lines of Python for implementing
127
128
  password (basic and parameters) and token authentications,
128
129
  admin group authorization, and routes for
129
130
  token generation (2), identity tests (2) and an incredible open cookie-based
@@ -15,6 +15,7 @@ types-flask
15
15
  types-requests
16
16
  FlaskSimpleAuth>=30.0
17
17
  passlib
18
+ pydantic
18
19
 
19
20
  [doc]
20
21
  sphinx
@@ -9,7 +9,9 @@ import re
9
9
  from typing import Any
10
10
  import importlib
11
11
  import logging
12
- import pytest
12
+ import pytest # for explicit fail calls, see _pytestFail
13
+ import json
14
+ import dataclasses
13
15
 
14
16
  log = logging.getLogger("flask_tester")
15
17
 
@@ -346,17 +348,59 @@ class Client:
346
348
  cookies.update(kwargs["cookies"])
347
349
  del kwargs["cookies"]
348
350
 
351
+ # FIXME allow or forbid?
352
+ # if "json" in kwargs and "data" in kwargs:
353
+ # log.warning("mix of json and data parameters in request")
354
+
355
+ # convert json parameters
356
+ if "json" in kwargs:
357
+ json_param = kwargs["json"]
358
+ assert isinstance(json_param, dict)
359
+ for name in list(json_param.keys()):
360
+ val = json_param[name]
361
+ if val is None:
362
+ pass
363
+ elif isinstance(val, (bool, int, float, str, tuple, list, dict)):
364
+ pass
365
+ elif "model_dump" in val.__dir__() and callable(val.model_dump):
366
+ # probably pydantic
367
+ json_param[name] = val.model_dump()
368
+ else: # pydantic or standard dataclasses?
369
+ json_param[name] = dataclasses.asdict(val)
370
+
371
+ # convert data parameters
372
+ if "data" in kwargs:
373
+ data_param = kwargs["data"]
374
+ assert isinstance(data_param, dict)
375
+ for name in list(data_param.keys()):
376
+ val = data_param[name]
377
+ if val is None:
378
+ data_param[name] = "null"
379
+ elif isinstance(val, (io.IOBase, tuple)):
380
+ pass # file parameters?
381
+ elif isinstance(val, (bool, int, float, str)):
382
+ # FIXME bool seems KO
383
+ pass
384
+ elif isinstance(val, (list, dict)):
385
+ data_param[name] = json.dumps(val)
386
+ elif "model_dump_json" in val.__dir__() and callable(val.model_dump_json):
387
+ data_param[name] = val.model_dump_json()
388
+ else:
389
+ data_param[name] = json.dumps(dataclasses.asdict(val))
390
+
349
391
  self._auth.setAuth(login, kwargs, cookies, auth=auth)
350
392
  res = self._request(method, path, cookies, **kwargs) # type: ignore
351
393
 
352
394
  # check status
353
395
  if status is not None:
354
396
  if res.status_code != status: # show error before aborting
397
+ # FIXME what if the useful part is at the end?
355
398
  _pytestFail(f"bad {status} result: {res.status_code} {res.text[:512]}...")
356
399
 
357
400
  # check content
358
401
  if content is not None:
359
402
  if not re.search(content, res.text, re.DOTALL):
403
+ # FIXME what if the useful part is at the end?
360
404
  _pytestFail(f"cannot find {content} in {res.text[:512]}...")
361
405
 
362
406
  return res
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: FlaskTester
3
- Version: 4.0
3
+ Version: 4.2
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: CC0
@@ -34,6 +34,7 @@ Requires-Dist: types-flask; extra == "dev"
34
34
  Requires-Dist: types-requests; extra == "dev"
35
35
  Requires-Dist: FlaskSimpleAuth>=30.0; extra == "dev"
36
36
  Requires-Dist: passlib; extra == "dev"
37
+ Requires-Dist: pydantic; extra == "dev"
37
38
  Provides-Extra: doc
38
39
  Requires-Dist: sphinx; extra == "doc"
39
40
  Requires-Dist: sphinx_rtd_theme; extra == "doc"
@@ -123,7 +124,7 @@ The above test runs with [`tests/app.py`](tests/app.py)
123
124
  [Flask](https://flask.palletsprojects.com/)
124
125
  REST application back-end with password and token authentication based on
125
126
  [FlaskSimpleAuth](https://pypi.org/project/FlaskSimpleAuth/).
126
- The code uses _25_ lines of Python for implementing
127
+ The code uses _23_ lines of Python for implementing
127
128
  password (basic and parameters) and token authentications,
128
129
  admin group authorization, and routes for
129
130
  token generation (2), identity tests (2) and an incredible open cookie-based
@@ -80,7 +80,7 @@ The above test runs with [`tests/app.py`](tests/app.py)
80
80
  [Flask](https://flask.palletsprojects.com/)
81
81
  REST application back-end with password and token authentication based on
82
82
  [FlaskSimpleAuth](https://pypi.org/project/FlaskSimpleAuth/).
83
- The code uses _25_ lines of Python for implementing
83
+ The code uses _23_ lines of Python for implementing
84
84
  password (basic and parameters) and token authentications,
85
85
  admin group authorization, and routes for
86
86
  token generation (2), identity tests (2) and an incredible open cookie-based
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "FlaskTester"
7
- version = "4.0"
7
+ version = "4.2"
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,14 @@ package = "https://pypi.org/project/FlaskTester/"
33
33
 
34
34
  [project.optional-dependencies]
35
35
  # various dev tools
36
- dev = ["mypy", "pyright", "ruff", "coverage", "pymarkdownlnt!=0.9.5", "build", "twine", "wheel", "types-flask", "types-requests", "FlaskSimpleAuth>=30.0", "passlib"]
36
+ dev = [
37
+ # static checks…
38
+ "mypy", "pyright", "ruff", "coverage", "pymarkdownlnt!=0.9.5",
39
+ # packaging
40
+ "build", "twine", "wheel",
41
+ # tests
42
+ "types-flask", "types-requests", "FlaskSimpleAuth>=30.0", "passlib", "pydantic"
43
+ ]
37
44
  # documentation generation
38
45
  doc = ["sphinx", "sphinx_rtd_theme", "sphinx-autoapi", "sphinx-lint", "myst_parser"]
39
46
 
@@ -8,6 +8,7 @@ import http.server as htsv
8
8
  import threading
9
9
  import io
10
10
  import logging
11
+ import model
11
12
 
12
13
  logging.basicConfig(level=logging.INFO)
13
14
  # logging.basicConfig(level=logging.DEBUG)
@@ -338,3 +339,47 @@ def test_client_fixture():
338
339
  # reset env
339
340
  if app:
340
341
  os.environ["FLASK_TESTER_APP"] = app
342
+
343
+ def test_classes(api):
344
+
345
+ def thing_eq(ta, tb):
346
+ tb = model.Thing1(**tb) if isinstance(tb, dict) else tb
347
+ return ta.tid == tb.tid and ta.name == tb.name and ta.owner == tb.owner
348
+
349
+ # Things
350
+ t0 = {"tid": 0, "name": "zero", "owner": "Rosalyn"}
351
+ t1 = model.Thing1(tid=1, name="one", owner="Susie")
352
+ t2 = model.Thing2(tid=2, name="two", owner="Calvin")
353
+ t3 = model.Thing3(tid=3, name="three", owner="Hobbes")
354
+
355
+ n = 0
356
+ # check all combinations
357
+ for path in ["/t0", "/t1", "/t2", "/t3"]:
358
+ for param in [t0, t1, t2, t3]:
359
+ for tclass in [model.Thing1, model.Thing2, model.Thing3]:
360
+ for method in ["GET", "POST"]:
361
+ for mode in ["data", "json"]:
362
+ n +=1
363
+ parameter = {mode: {"t": param}}
364
+ res = api.request(method, path, 200, **parameter)
365
+ assert res.is_json
366
+ json = res.json
367
+ assert isinstance(json, dict)
368
+ assert thing_eq(tclass(**json), param)
369
+ assert n == 192
370
+
371
+ # simple types translation
372
+ assert api.get("/t0", 200, json={"t": None}).json is None
373
+ assert api.get("/t0", 200, data={"t": None}).json is None
374
+ assert api.get("/t0", 200, json={"t": True}).json == True
375
+ assert api.get("/t0", 200, json={"t": False}).json == False
376
+ # non working, bad JsonData conversion
377
+ api.get("/t0", 400, data={"t": True})
378
+ api.get("/t0", 400, data={"t": False})
379
+ assert api.get("/t0", 200, json={"t": 2345}).json == 2345
380
+ assert api.get("/t0", 200, data={"t": 5432}).json == 5432
381
+ assert api.get("/t0", 200, json={"t": 23.45}).json == 23.45
382
+ assert api.get("/t0", 200, data={"t": 54.32}).json == 54.32
383
+ assert api.get("/t0", 200, json={"t": "Susie"}).json == "Susie"
384
+ api.get("/t0", 400, data={"t": "Susie"}) # raw str is not JSON
385
+ assert api.get("/t0", 200, data={"t": "\"Susie\""}).json == "Susie"
File without changes
File without changes
File without changes
File without changes