5 Commits

Author SHA1 Message Date
27c576db21 Merge pull request 'dev' (#1) from dev into master
Reviewed-on: https://chacha.ddns.net/gitea/chacha/pyrestresource/pulls/1
new-tag:0.1.0
2023-11-06 16:07:12 +01:00
cclecle
6311d90a2d update jenkins & toml from project template 2023-11-06 14:56:46 +00:00
cclecle
7e13d49feb remove unused data dir from toml 2023-11-06 13:57:00 +00:00
cclecle
9b3e847908 use threaded uvicorn during test 2023-11-06 13:50:34 +00:00
cclecle
3afebdba33 temporary disable PERF tests 2023-11-06 11:25:49 +00:00
8 changed files with 104 additions and 103 deletions

14
Jenkinsfile vendored
View File

@@ -426,9 +426,17 @@ pipeline {
}
post {
always {
dir("gitrepo") {
publishCoverage adapters: [cobertura(mergeToOneReport: true, path: "helpers-results/cl_types_check/cobertura.xml")]
junit 'helpers-results/cl_types_check/junit.xml'
dir("gitrepo") {
//publish coverage
recordCoverage( sourceDirectories: [[path: 'src']],
tools: [[parser: 'COBERTURA', pattern: 'helpers-results/cl_types_check/cobertura.xml']],
id: 'COBERTURA', name: 'COBERTURA Coverage',
sourceCodeRetention: 'EVERY_BUILD',)
//add type check to junit result set
junit 'helpers-results/cl_types_check/junit.xml'
//publish html reports files
publishHTML([
reportDir: "helpers-results/cl_quality_check",
reportFiles: "report.html",

View File

@@ -12,21 +12,23 @@
A RESTful API library built on top of pydantic & uvicorn to make service API from a data model.
/!\ early in-progress project for internal use ATM.
/!\\ early in-progress project for internal use ATM.
Feel free to contribute.
Features:
- use annotation
Features (available):
- type annotation used
- support containers (dict)
- support plugins (for hook and biding)
- user authentification (WIP)
- ACL (WIP)
- python internal model instance (with possible serialization/auto-save on-disk)
- user auth
- ACL
- daemon mode
Features(planned):
- group support
- python internal model instance (with possible serialization/auto-save on-disk)
Limitations:
- no nested reads / writes
- weak unitest (atm)
Checkout [Latest Documentation](https://chacha.ddns.net/mkdocs-web/chacha/pyrestresource/master/latest/).

View File

@@ -48,7 +48,6 @@ include-package-data = true
where = ["src"]
[tool.setuptools.package-data]
"pyrestresource.data" = ["*.*"]
"pyrestresource" = ["py.typed"]
# [[tool.mypy.overrides]]
@@ -56,10 +55,13 @@ where = ["src"]
# ignore_missing_imports = true
[tool.coverage.run]
cover_pylib = false
branch = true
data_file="helpers-results/cl_unit_test_raw_coverage/.coverage"
# debug = ["config","multiproc","process"]
parallel = true
concurrency = [
'thread',
'multiprocessing'
'thread'
]
[project.urls]
@@ -68,12 +70,12 @@ Documentation = "https://chacha.ddns.net/mkdocs-web/chacha/pyrestresource/mast
Tracker = "https://chacha.ddns.net/gitea/chacha/pyrestresource/issues"
[project.optional-dependencies]
test = ["chacha_cicd_helper@git+https://chacha.ddns.net/gitea/chacha/chacha_cicd_helper.git@master"]
coverage-check = ["chacha_cicd_helper@git+https://chacha.ddns.net/gitea/chacha/chacha_cicd_helper.git@master"]
complexity-check = ["chacha_cicd_helper@git+https://chacha.ddns.net/gitea/chacha/chacha_cicd_helper.git@master"]
quality-check = ["chacha_cicd_helper@git+https://chacha.ddns.net/gitea/chacha/chacha_cicd_helper.git@master"]
type-check = ["chacha_cicd_helper@git+https://chacha.ddns.net/gitea/chacha/chacha_cicd_helper.git@master"]
doc-gen = ["chacha_cicd_helper@git+https://chacha.ddns.net/gitea/chacha/chacha_cicd_helper.git@master"]
test = ["chacha_cicd_helper"]
coverage-check = ["chacha_cicd_helper"]
complexity-check = ["chacha_cicd_helper"]
quality-check = ["chacha_cicd_helper"]
type-check = ["chacha_cicd_helper"]
doc-gen = ["chacha_cicd_helper"]
# [project.scripts]
# my-script = "my_package.module:function"

23
test/ThreadedUvicorn.py Normal file
View File

@@ -0,0 +1,23 @@
from uvicorn import Config, Server
from threading import Thread
import asyncio
class ThreadedUvicorn:
def __init__(self, config: Config):
self.server = Server(config)
self.thread = Thread(daemon=True, target=self.server.run)
def start(self):
self.thread.start()
asyncio.run(self.wait_for_started())
async def wait_for_started(self):
while not self.server.started:
await asyncio.sleep(0.1)
def stop(self):
if self.thread.is_alive():
self.server.should_exit = True
while self.thread.is_alive():
continue

View File

@@ -5,3 +5,5 @@
#
# You should have received a copy of the license along with this
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
from .ThreadedUvicorn import ThreadedUvicorn

View File

@@ -32,6 +32,8 @@ from src.pyrestresource import (
)
from test import ThreadedUvicorn
testdir_path = Path(__file__).parent.resolve()
chdir(testdir_path.parent.resolve())
@@ -93,26 +95,18 @@ def find_free_port():
return "localhost", s.getsockname()[1]
def launch_server(ip, port):
init_classes()
uvicorn.run(f"{__loader__.name}:RootApp", port=port, host="0.0.0.0", log_level="warning", factory=True)
class Test_RestAPI_LOGIN_Web(unittest.TestCase):
def setUp(self) -> None:
chdir(testdir_path.parent.resolve())
def test_login_two_users(self):
ip, port = find_free_port()
proc = Process(
target=launch_server,
args=(
ip,
port,
),
)
proc.start()
init_classes()
server = ThreadedUvicorn(uvicorn.Config(f"{__loader__.name}:RootApp", port=port, host="0.0.0.0", log_level="warning", factory=True))
server.start()
sleep(1)
s = requests.Session()
s.mount("http://", HTTPAdapter(max_retries=0))
@@ -206,19 +200,15 @@ class Test_RestAPI_LOGIN_Web(unittest.TestCase):
self.assertEqual(response.json(), "A TEST SET VALUE 2")
finally:
proc.terminate()
s.close()
server.stop()
def test_login(self):
ip, port = find_free_port()
proc = Process(
target=launch_server,
args=(
ip,
port,
),
)
proc.start()
init_classes()
server = ThreadedUvicorn(uvicorn.Config(f"{__loader__.name}:RootApp", port=port, host="0.0.0.0", log_level="warning", factory=True))
server.start()
sleep(1)
s = requests.Session()
s.mount("http://", HTTPAdapter(max_retries=0))
@@ -260,19 +250,15 @@ class Test_RestAPI_LOGIN_Web(unittest.TestCase):
self.assertEqual(response.json(), "TestUser")
finally:
proc.terminate()
s.close()
server.stop()
def test_change_host(self):
ip, port = find_free_port()
proc = Process(
target=launch_server,
args=(
ip,
port,
),
)
proc.start()
init_classes()
server = ThreadedUvicorn(uvicorn.Config(f"{__loader__.name}:RootApp", port=port, host="0.0.0.0", log_level="warning", factory=True))
server.start()
sleep(1)
s1 = requests.Session()
s1.mount("http://", HTTPAdapter(max_retries=0))
@@ -378,20 +364,16 @@ class Test_RestAPI_LOGIN_Web(unittest.TestCase):
self.assertEqual(response.json(), "__ANNONYMOUS__")
finally:
proc.terminate()
s1.close()
s2.close()
server.stop()
def test_login_wrong_pwd(self):
ip, port = find_free_port()
proc = Process(
target=launch_server,
args=(
ip,
port,
),
)
proc.start()
init_classes()
server = ThreadedUvicorn(uvicorn.Config(f"{__loader__.name}:RootApp", port=port, host="0.0.0.0", log_level="warning", factory=True))
server.start()
sleep(1)
s = requests.Session()
s.mount("http://", HTTPAdapter(max_retries=0))
@@ -493,19 +475,15 @@ class Test_RestAPI_LOGIN_Web(unittest.TestCase):
self.assertDictEqual(s.cookies.get_dict(), {})
finally:
proc.terminate()
s.close()
server.stop()
def test_access_resourceACL(self):
ip, port = find_free_port()
proc = Process(
target=launch_server,
args=(
ip,
port,
),
)
proc.start()
init_classes()
server = ThreadedUvicorn(uvicorn.Config(f"{__loader__.name}:RootApp", port=port, host="0.0.0.0", log_level="warning", factory=True))
server.start()
sleep(1)
s = requests.Session()
s.mount("http://", HTTPAdapter(max_retries=0))
@@ -570,19 +548,15 @@ class Test_RestAPI_LOGIN_Web(unittest.TestCase):
self.assertEqual(response.json(), "TEST SET VALUE 2")
finally:
proc.terminate()
s.close()
server.stop()
def test_access_fieldACL(self):
ip, port = find_free_port()
proc = Process(
target=launch_server,
args=(
ip,
port,
),
)
proc.start()
init_classes()
server = ThreadedUvicorn(uvicorn.Config(f"{__loader__.name}:RootApp", port=port, host="0.0.0.0", log_level="warning", factory=True))
server.start()
sleep(1)
s = requests.Session()
s.mount("http://", HTTPAdapter(max_retries=0))
@@ -647,5 +621,5 @@ class Test_RestAPI_LOGIN_Web(unittest.TestCase):
self.assertEqual(response.json(), "TEST SET VALUE 2")
finally:
proc.terminate()
s.close()
server.stop()

View File

@@ -484,7 +484,7 @@ class Test_RestAPI_PERFO(unittest.TestCase):
init_classes()
self.testapp = RootApp()
# @unittest.skip
@unittest.skip
def test_perf_dict(self):
print(f"LIB INTERNAL PERF TEST")
n_loop = 10000

View File

@@ -13,6 +13,7 @@ import requests
from contextlib import closing
from multiprocessing import Process
from requests.adapters import HTTPAdapter
import coverage
print(__name__)
print(__package__)
@@ -29,6 +30,8 @@ from src.pyrestresource import (
)
from pprint import pprint
from test import ThreadedUvicorn
testdir_path = Path(__file__).parent.resolve()
chdir(testdir_path.parent.resolve())
@@ -120,25 +123,16 @@ def find_free_port():
return "localhost", s.getsockname()[1]
def launch_server(ip, port):
init_classes()
uvicorn.run(f"{__loader__.name}:RootApp", port=port, host="0.0.0.0", log_level="warning", factory=True)
class Test_RestAPI_WebServer(unittest.TestCase):
def setUp(self) -> None:
chdir(testdir_path.parent.resolve())
def test_nomal_AllCmd_games(self):
ip, port = find_free_port()
proc = Process(
target=launch_server,
args=(
ip,
port,
),
)
proc.start()
init_classes()
server = ThreadedUvicorn(uvicorn.Config(f"{__loader__.name}:RootApp", port=port, host="0.0.0.0", log_level="warning", factory=True))
server.start()
sleep(1)
s = requests.Session()
s.mount("http://", HTTPAdapter(max_retries=0))
@@ -272,23 +266,19 @@ class Test_RestAPI_WebServer(unittest.TestCase):
data = response.json()
self.assertTrue(len(data) == 0)
finally:
proc.terminate()
s.close()
server.stop()
# @unittest.skip
@unittest.skip
def test_perf_dict(self):
print(f"SOCKET PERF TEST")
n_loop = 10000
ip, port = find_free_port()
proc = Process(
target=launch_server,
args=(
ip,
port,
),
)
proc.start()
init_classes()
server = ThreadedUvicorn(uvicorn.Config(f"{__loader__.name}:RootApp", port=port, host="0.0.0.0", log_level="warning", factory=True))
server.start()
sleep(1)
s = requests.Session()
s.mount("http://", HTTPAdapter(max_retries=0))
@@ -366,5 +356,5 @@ class Test_RestAPI_WebServer(unittest.TestCase):
print(f"PUT/GET 2nd level (value) dict: {int(n_loop/(end-start))} Req/s")
finally:
proc.terminate()
s.close()
server.stop()