tcboard 0.1.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.
- tcboard-0.1.0/.gitignore +10 -0
- tcboard-0.1.0/.pre-commit-config.yaml +26 -0
- tcboard-0.1.0/LICENSE +21 -0
- tcboard-0.1.0/PKG-INFO +82 -0
- tcboard-0.1.0/README.md +43 -0
- tcboard-0.1.0/app.py +170 -0
- tcboard-0.1.0/conftest.py +3 -0
- tcboard-0.1.0/pyproject.toml +98 -0
- tcboard-0.1.0/tcboard/__init__.py +19 -0
- tcboard-0.1.0/tcboard/_version.py +24 -0
- tcboard-0.1.0/tcboard/alert.py +71 -0
- tcboard-0.1.0/tcboard/assets/favicon.ico +0 -0
- tcboard-0.1.0/tcboard/board.py +555 -0
- tcboard-0.1.0/tcboard/cli/__init__.py +0 -0
- tcboard-0.1.0/tcboard/cli/api.py +159 -0
- tcboard-0.1.0/tcboard/cli/db.py +182 -0
- tcboard-0.1.0/tcboard/cli/debug.py +108 -0
- tcboard-0.1.0/tcboard/cli/main.py +289 -0
- tcboard-0.1.0/tcboard/cli/remotectrl.py +167 -0
- tcboard-0.1.0/tcboard/cli/replay.py +139 -0
- tcboard-0.1.0/tcboard/cli/squoremqtt.py +239 -0
- tcboard-0.1.0/tcboard/cli/tptools.py +169 -0
- tcboard-0.1.0/tcboard/cli/util.py +75 -0
- tcboard-0.1.0/tcboard/cli/ws.py +476 -0
- tcboard-0.1.0/tcboard/courtstate.py +75 -0
- tcboard-0.1.0/tcboard/dbmanager.py +194 -0
- tcboard-0.1.0/tcboard/devinfo.py +42 -0
- tcboard-0.1.0/tcboard/exceptions.py +30 -0
- tcboard-0.1.0/tcboard/ext/__init__.py +0 -0
- tcboard-0.1.0/tcboard/ext/squore/__init__.py +9 -0
- tcboard-0.1.0/tcboard/ext/squore/devinfo.py +28 -0
- tcboard-0.1.0/tcboard/ext/squore/livedata.py +243 -0
- tcboard-0.1.0/tcboard/ext/squore/match.py +101 -0
- tcboard-0.1.0/tcboard/ext/squore/point.py +69 -0
- tcboard-0.1.0/tcboard/ext/tptools/__init__.py +0 -0
- tcboard-0.1.0/tcboard/ext/tptools/livedata.py +61 -0
- tcboard-0.1.0/tcboard/game.py +33 -0
- tcboard-0.1.0/tcboard/livedata.py +114 -0
- tcboard-0.1.0/tcboard/livestatus.py +61 -0
- tcboard-0.1.0/tcboard/match.py +91 -0
- tcboard-0.1.0/tcboard/matchslot.py +12 -0
- tcboard-0.1.0/tcboard/matchstate.py +241 -0
- tcboard-0.1.0/tcboard/modelabc.py +66 -0
- tcboard-0.1.0/tcboard/player.py +24 -0
- tcboard-0.1.0/tcboard/point.py +16 -0
- tcboard-0.1.0/tcboard/tournament.py +9 -0
- tcboard-0.1.0/tests/__init__.py +0 -0
- tcboard-0.1.0/tests/cli/__init__.py +0 -0
- tcboard-0.1.0/tests/cli/conftest.py +41 -0
- tcboard-0.1.0/tests/cli/test_clicontext.py +8 -0
- tcboard-0.1.0/tests/cli/test_main.py +8 -0
- tcboard-0.1.0/tests/cli/test_util.py +59 -0
- tcboard-0.1.0/tests/conftest.py +261 -0
- tcboard-0.1.0/tests/ext/__init__.py +0 -0
- tcboard-0.1.0/tests/ext/squore/__init__.py +0 -0
- tcboard-0.1.0/tests/ext/squore/conftest.py +255 -0
- tcboard-0.1.0/tests/ext/squore/test_devinfo.py +13 -0
- tcboard-0.1.0/tests/ext/squore/test_livedata.py +275 -0
- tcboard-0.1.0/tests/ext/squore/test_match.py +5 -0
- tcboard-0.1.0/tests/ext/squore/test_point.py +62 -0
- tcboard-0.1.0/tests/ext/tptools/__init__.py +0 -0
- tcboard-0.1.0/tests/ext/tptools/test_livedata.py +92 -0
- tcboard-0.1.0/tests/test_alert.py +107 -0
- tcboard-0.1.0/tests/test_board.py +74 -0
- tcboard-0.1.0/tests/test_courtstate.py +76 -0
- tcboard-0.1.0/tests/test_dbmanager.py +159 -0
- tcboard-0.1.0/tests/test_devinfo.py +28 -0
- tcboard-0.1.0/tests/test_exception.py +12 -0
- tcboard-0.1.0/tests/test_game.py +6 -0
- tcboard-0.1.0/tests/test_livedata.py +99 -0
- tcboard-0.1.0/tests/test_livestatus.py +33 -0
- tcboard-0.1.0/tests/test_match.py +47 -0
- tcboard-0.1.0/tests/test_matchslot.py +8 -0
- tcboard-0.1.0/tests/test_matchstate.py +218 -0
- tcboard-0.1.0/tests/test_modelabc.py +40 -0
- tcboard-0.1.0/watchtests.sh +23 -0
tcboard-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# See https://pre-commit.com for more information
|
|
2
|
+
# See https://pre-commit.com/hooks.html for more hooks
|
|
3
|
+
repos:
|
|
4
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
5
|
+
rev: v2.4.0
|
|
6
|
+
hooks:
|
|
7
|
+
- id: mypy
|
|
8
|
+
additional_dependencies:
|
|
9
|
+
- pydantic
|
|
10
|
+
- click
|
|
11
|
+
- click_extra (>= 9.4.1)
|
|
12
|
+
- fastapi
|
|
13
|
+
- starlette
|
|
14
|
+
- pytest
|
|
15
|
+
- pytest-asyncio
|
|
16
|
+
- tptools
|
|
17
|
+
- click-async-plugins (>= 0.7.4)
|
|
18
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
19
|
+
rev: v0.16.10
|
|
20
|
+
hooks:
|
|
21
|
+
- id: ruff-check
|
|
22
|
+
args: [--show-fixes]
|
|
23
|
+
- id: ruff-format
|
|
24
|
+
args: [--diff]
|
|
25
|
+
|
|
26
|
+
fail_fast: true
|
tcboard-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 martin f. krafft
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tcboard-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tcboard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Server to collect and provide tournament data
|
|
5
|
+
Author-email: "martin f. krafft" <tcboard@pobox.madduck.net>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.13
|
|
11
|
+
Requires-Dist: aiomqtt
|
|
12
|
+
Requires-Dist: aiosqlite
|
|
13
|
+
Requires-Dist: aiostream
|
|
14
|
+
Requires-Dist: click
|
|
15
|
+
Requires-Dist: click-async-plugins>=0.7.4
|
|
16
|
+
Requires-Dist: click-extra>=9.4.1
|
|
17
|
+
Requires-Dist: fastapi
|
|
18
|
+
Requires-Dist: pydantic
|
|
19
|
+
Requires-Dist: tabulate
|
|
20
|
+
Requires-Dist: tptools
|
|
21
|
+
Requires-Dist: uvicorn
|
|
22
|
+
Requires-Dist: uvloop; sys_platform == 'linux'
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: click-extra[pytest]; extra == 'dev'
|
|
25
|
+
Requires-Dist: coverage; extra == 'dev'
|
|
26
|
+
Requires-Dist: fastapi[standard]; extra == 'dev'
|
|
27
|
+
Requires-Dist: ipdb; extra == 'dev'
|
|
28
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
29
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-mock; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-ruff; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
36
|
+
Requires-Dist: types-python-dateutil; extra == 'dev'
|
|
37
|
+
Requires-Dist: types-tabulate; extra == 'dev'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
[](https://github.com/madduck/tcboard/actions/workflows/0-testing.yml)
|
|
41
|
+
|
|
42
|
+
# TCBoard — Server to collect and provide tournament data
|
|
43
|
+
|
|
44
|
+
TODO
|
|
45
|
+
|
|
46
|
+
## Contributing
|
|
47
|
+
|
|
48
|
+
To contribute, please ensure you have the appropriate dependencies installed:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
pip install -e .[dev]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
and then install the Git pre-commit hooks that ensure that any commits conform
|
|
55
|
+
with the coding-style used by this project.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
pre-commit install
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
All code (except for the CLI) is 100% test-covered, and all
|
|
62
|
+
contributions are expected to keep this up. Use `pytest` to run the test suite.
|
|
63
|
+
|
|
64
|
+
Note that all code is typed, and typing is part of test-coverage.
|
|
65
|
+
|
|
66
|
+
## Known Problems
|
|
67
|
+
|
|
68
|
+
None at this point (but there will be some).
|
|
69
|
+
|
|
70
|
+
## TODO
|
|
71
|
+
|
|
72
|
+
In addition to various comments including the word "TODO" in the code, there are
|
|
73
|
+
a few things left to do:
|
|
74
|
+
|
|
75
|
+
* Test the CLI
|
|
76
|
+
* Test the HTTP APIs
|
|
77
|
+
|
|
78
|
+
## Legalese
|
|
79
|
+
|
|
80
|
+
`tcboard` is © 2024–6 martin f. krafft <tcboard@pobox.madduck.net>.
|
|
81
|
+
|
|
82
|
+
It is released under the terms of the MIT Licence.
|
tcboard-0.1.0/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[](https://github.com/madduck/tcboard/actions/workflows/0-testing.yml)
|
|
2
|
+
|
|
3
|
+
# TCBoard — Server to collect and provide tournament data
|
|
4
|
+
|
|
5
|
+
TODO
|
|
6
|
+
|
|
7
|
+
## Contributing
|
|
8
|
+
|
|
9
|
+
To contribute, please ensure you have the appropriate dependencies installed:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
pip install -e .[dev]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
and then install the Git pre-commit hooks that ensure that any commits conform
|
|
16
|
+
with the coding-style used by this project.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
pre-commit install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
All code (except for the CLI) is 100% test-covered, and all
|
|
23
|
+
contributions are expected to keep this up. Use `pytest` to run the test suite.
|
|
24
|
+
|
|
25
|
+
Note that all code is typed, and typing is part of test-coverage.
|
|
26
|
+
|
|
27
|
+
## Known Problems
|
|
28
|
+
|
|
29
|
+
None at this point (but there will be some).
|
|
30
|
+
|
|
31
|
+
## TODO
|
|
32
|
+
|
|
33
|
+
In addition to various comments including the word "TODO" in the code, there are
|
|
34
|
+
a few things left to do:
|
|
35
|
+
|
|
36
|
+
* Test the CLI
|
|
37
|
+
* Test the HTTP APIs
|
|
38
|
+
|
|
39
|
+
## Legalese
|
|
40
|
+
|
|
41
|
+
`tcboard` is © 2024–6 martin f. krafft <tcboard@pobox.madduck.net>.
|
|
42
|
+
|
|
43
|
+
It is released under the terms of the MIT Licence.
|
tcboard-0.1.0/app.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import pathlib
|
|
5
|
+
import sys
|
|
6
|
+
import warnings
|
|
7
|
+
from collections.abc import AsyncGenerator
|
|
8
|
+
from contextlib import AsyncExitStack, asynccontextmanager
|
|
9
|
+
from functools import partial
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
from click_async_plugins import (
|
|
13
|
+
ITC,
|
|
14
|
+
PluginFactory,
|
|
15
|
+
create_plugin_task,
|
|
16
|
+
setup_plugins,
|
|
17
|
+
)
|
|
18
|
+
from fastapi import FastAPI
|
|
19
|
+
from httpx2 import URL
|
|
20
|
+
from tptools.util import is_truish, silence_logger
|
|
21
|
+
|
|
22
|
+
from tcboard.board import TCBoard
|
|
23
|
+
from tcboard.cli.api import configure_api
|
|
24
|
+
from tcboard.cli.db import database_backend
|
|
25
|
+
from tcboard.cli.debug import debug_key_press_handler
|
|
26
|
+
from tcboard.cli.main import (
|
|
27
|
+
make_app,
|
|
28
|
+
on_board_update,
|
|
29
|
+
process_squore_livedata,
|
|
30
|
+
receive_tournament,
|
|
31
|
+
)
|
|
32
|
+
from tcboard.cli.remotectrl import remote_control_publisher
|
|
33
|
+
from tcboard.cli.squoremqtt import listen_for_mqtt_messages
|
|
34
|
+
from tcboard.cli.tptools import setup_for_tptools
|
|
35
|
+
from tcboard.cli.util import CliContext
|
|
36
|
+
from tcboard.cli.ws import configure_for_websockets
|
|
37
|
+
|
|
38
|
+
logging.getLogger().setLevel(logging.DEBUG)
|
|
39
|
+
|
|
40
|
+
if not sys.warnoptions:
|
|
41
|
+
warnings.simplefilter("default")
|
|
42
|
+
logging.captureWarnings(True)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
THIRD_PARTY_LOG_LEVELS = [
|
|
46
|
+
("asyncio", logging.INFO),
|
|
47
|
+
("aiosqlite", logging.INFO),
|
|
48
|
+
("watchfiles", logging.WARNING),
|
|
49
|
+
("uvicorn", logging.INFO),
|
|
50
|
+
("uvicorn.error", logging.WARNING),
|
|
51
|
+
("click_async_plugins", logging.DEBUG),
|
|
52
|
+
("httpcore2", logging.INFO),
|
|
53
|
+
("httpx2", logging.INFO),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
if is_truish(os.getenv("LESS_DEBUG", os.getenv("LESSDEBUG"))):
|
|
57
|
+
LOG_LEVELS = [
|
|
58
|
+
(__name__, logging.INFO),
|
|
59
|
+
("tptools", logging.INFO),
|
|
60
|
+
("tcboard.cli", logging.INFO),
|
|
61
|
+
("tcboard.dbmanager", logging.INFO),
|
|
62
|
+
("tcboard.board", logging.INFO),
|
|
63
|
+
]
|
|
64
|
+
else:
|
|
65
|
+
LOG_LEVELS = []
|
|
66
|
+
|
|
67
|
+
for name, level in LOG_LEVELS + THIRD_PARTY_LOG_LEVELS:
|
|
68
|
+
silence_logger(name, level=level)
|
|
69
|
+
|
|
70
|
+
logger = logging.getLogger(__name__)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
MQTT_BROKER = os.getenv("MQTT_BROKER", "127.0.0.1:1883")
|
|
74
|
+
|
|
75
|
+
DB_FILE: pathlib.Path | Literal[":memory:"] | None
|
|
76
|
+
if (envfile := os.getenv("DB", os.getenv("DB_FILE"))) is None:
|
|
77
|
+
DB_FILE = pathlib.Path(__file__).parent / "db.sqlite"
|
|
78
|
+
|
|
79
|
+
elif envfile == ":memory:":
|
|
80
|
+
DB_FILE = None
|
|
81
|
+
|
|
82
|
+
else:
|
|
83
|
+
DB_FILE = pathlib.Path(envfile)
|
|
84
|
+
|
|
85
|
+
database_backend = partial(database_backend, file=DB_FILE)
|
|
86
|
+
|
|
87
|
+
debug_match_ids: list[str] = []
|
|
88
|
+
if dmis := os.getenv("DEBUG_MATCH_IDS", ""):
|
|
89
|
+
debug_match_ids = dmis.split(",")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@asynccontextmanager
|
|
93
|
+
async def app_lifespan(api: FastAPI) -> AsyncGenerator[None]:
|
|
94
|
+
board = TCBoard(debug_match_ids=debug_match_ids)
|
|
95
|
+
itc = ITC()
|
|
96
|
+
itc.set("board", board)
|
|
97
|
+
|
|
98
|
+
clictx = CliContext(api=api, itc=itc)
|
|
99
|
+
|
|
100
|
+
board.register_update_function(partial(on_board_update, itc=itc))
|
|
101
|
+
|
|
102
|
+
broker = MQTT_BROKER.split(":")
|
|
103
|
+
port: int | str
|
|
104
|
+
if len(broker) == 3:
|
|
105
|
+
proto, host, port = broker
|
|
106
|
+
assert proto == "mqtt"
|
|
107
|
+
host = host[2:]
|
|
108
|
+
|
|
109
|
+
elif len(broker) == 2:
|
|
110
|
+
host, port = broker
|
|
111
|
+
|
|
112
|
+
else:
|
|
113
|
+
port = 1883
|
|
114
|
+
host = broker[0]
|
|
115
|
+
|
|
116
|
+
squoremqtt_client = partial(
|
|
117
|
+
listen_for_mqtt_messages, server=host, port=int(port), ignore_retained=True
|
|
118
|
+
)
|
|
119
|
+
remote_control = partial(remote_control_publisher)
|
|
120
|
+
|
|
121
|
+
BOOTSTRAP_URL = os.getenv("BOOTSTRAP_URL")
|
|
122
|
+
tptools = partial(
|
|
123
|
+
setup_for_tptools,
|
|
124
|
+
load_from_url=None if BOOTSTRAP_URL is None else URL(BOOTSTRAP_URL),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
factories: list[PluginFactory] = [
|
|
128
|
+
debug_key_press_handler,
|
|
129
|
+
database_backend,
|
|
130
|
+
tptools,
|
|
131
|
+
receive_tournament,
|
|
132
|
+
process_squore_livedata,
|
|
133
|
+
squoremqtt_client,
|
|
134
|
+
remote_control,
|
|
135
|
+
configure_for_websockets,
|
|
136
|
+
configure_api,
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
async with AsyncExitStack() as stack:
|
|
141
|
+
tasks = await setup_plugins(factories, stack=stack, clictx=clictx)
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
async with asyncio.TaskGroup() as tg:
|
|
145
|
+
plugin_task = partial(
|
|
146
|
+
create_plugin_task, create_task_fn=tg.create_task
|
|
147
|
+
)
|
|
148
|
+
for task in tasks:
|
|
149
|
+
plugin_task(task)
|
|
150
|
+
logger.debug("Tasks:")
|
|
151
|
+
for t in tg._tasks:
|
|
152
|
+
logger.debug(f" {t}")
|
|
153
|
+
yield
|
|
154
|
+
raise asyncio.CancelledError
|
|
155
|
+
|
|
156
|
+
except asyncio.CancelledError:
|
|
157
|
+
logger.info("Exiting…")
|
|
158
|
+
|
|
159
|
+
except* RuntimeError:
|
|
160
|
+
logger.exception("A runtime error occurred")
|
|
161
|
+
|
|
162
|
+
except* Exception as exc:
|
|
163
|
+
import ipdb
|
|
164
|
+
|
|
165
|
+
logger.exception("")
|
|
166
|
+
ipdb.set_trace() # noqa: E402 E702 I001 # fmt: skip
|
|
167
|
+
_ = exc
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
app = make_app(lifespan=app_lifespan)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[tool.hatch.build.hooks.vcs]
|
|
6
|
+
version-file = "tcboard/_version.py"
|
|
7
|
+
|
|
8
|
+
[tool.hatch.version]
|
|
9
|
+
source = "vcs"
|
|
10
|
+
|
|
11
|
+
[tool.hatch.build.targets.sdist]
|
|
12
|
+
exclude = [".direnv", ".gitignore", ".github"]
|
|
13
|
+
|
|
14
|
+
[project]
|
|
15
|
+
name = "tcboard"
|
|
16
|
+
dynamic = ["version"]
|
|
17
|
+
authors = [{ name = "martin f. krafft", email = "tcboard@pobox.madduck.net" }]
|
|
18
|
+
description = "Server to collect and provide tournament data"
|
|
19
|
+
readme = "README.md"
|
|
20
|
+
requires-python = ">=3.13"
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
dependencies = [
|
|
28
|
+
"tptools",
|
|
29
|
+
"pydantic",
|
|
30
|
+
"uvloop; sys_platform == 'linux'",
|
|
31
|
+
"click",
|
|
32
|
+
"click-extra (>= 9.4.1)",
|
|
33
|
+
"click-async-plugins (>= 0.7.4)",
|
|
34
|
+
"fastapi",
|
|
35
|
+
"uvicorn",
|
|
36
|
+
"aiomqtt",
|
|
37
|
+
"aiosqlite",
|
|
38
|
+
"aiostream",
|
|
39
|
+
"tabulate",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = [
|
|
44
|
+
"fastapi[standard]",
|
|
45
|
+
"pytest",
|
|
46
|
+
"pytest-mock",
|
|
47
|
+
"pytest-asyncio",
|
|
48
|
+
"pytest-ruff",
|
|
49
|
+
"click-extra[pytest]",
|
|
50
|
+
"ipdb",
|
|
51
|
+
"mypy",
|
|
52
|
+
"ruff",
|
|
53
|
+
"pre-commit",
|
|
54
|
+
"coverage",
|
|
55
|
+
"pytest-cov",
|
|
56
|
+
"types-tabulate",
|
|
57
|
+
"types-python-dateutil",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[project.scripts]
|
|
61
|
+
tcboard = "tcboard.cli.main:tcboard"
|
|
62
|
+
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
testpaths = ["tests", "integration"]
|
|
65
|
+
addopts = """
|
|
66
|
+
-ra
|
|
67
|
+
--pdbcls=IPython.terminal.debugger:TerminalPdb
|
|
68
|
+
--cov=tcboard --cov-report term-missing:skip-covered --no-cov-on-fail
|
|
69
|
+
--ruff --ruff-format --ruff-config 'lint.ignore = ["I001", "F401", "F841"]'
|
|
70
|
+
"""
|
|
71
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
72
|
+
log_level = "WARNING"
|
|
73
|
+
log_format = "%(name)s %(levelname)s %(message)s"
|
|
74
|
+
|
|
75
|
+
[tool.ruff]
|
|
76
|
+
line-length = 88
|
|
77
|
+
|
|
78
|
+
[tool.ruff.lint]
|
|
79
|
+
select = ["B", "C", "E", "F", "I", "W", "B9"]
|
|
80
|
+
|
|
81
|
+
[tool.mypy]
|
|
82
|
+
strict = true
|
|
83
|
+
warn_unreachable = true
|
|
84
|
+
|
|
85
|
+
[[tool.mypy.overrides]]
|
|
86
|
+
module = "ipdb"
|
|
87
|
+
ignore_missing_imports = true
|
|
88
|
+
|
|
89
|
+
[tool.coverage.report]
|
|
90
|
+
exclude_also = [
|
|
91
|
+
'def __repr__',
|
|
92
|
+
'if TYPE_CHECKING:',
|
|
93
|
+
'class .*\bProtocol\):',
|
|
94
|
+
'@overload',
|
|
95
|
+
'@(abc\.)?abstractmethod',
|
|
96
|
+
'raise NotImplementedError',
|
|
97
|
+
'raise AssertionError',
|
|
98
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .board import TCBoard
|
|
2
|
+
from .exceptions import TCBoardException
|
|
3
|
+
from .match import TCMatch
|
|
4
|
+
from .tournament import TCTournament
|
|
5
|
+
|
|
6
|
+
VERSION: str | None
|
|
7
|
+
try:
|
|
8
|
+
from ._version import version as VERSION
|
|
9
|
+
|
|
10
|
+
except ImportError: # pragma: nocover
|
|
11
|
+
Version = None
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"TCBoard",
|
|
15
|
+
"TCBoardException",
|
|
16
|
+
"TCMatch",
|
|
17
|
+
"TCTournament",
|
|
18
|
+
"VERSION",
|
|
19
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# needed < 3.14 so that annotations aren't evaluated
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import traceback
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from types import TracebackType
|
|
8
|
+
from typing import Any, Self, cast
|
|
9
|
+
|
|
10
|
+
from pydantic import (
|
|
11
|
+
UUID4,
|
|
12
|
+
BaseModel,
|
|
13
|
+
Field,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Alert(BaseModel):
|
|
18
|
+
text: str
|
|
19
|
+
id: UUID4 = Field(default_factory=uuid.uuid4)
|
|
20
|
+
detail: str | None = None
|
|
21
|
+
matchid: str | None = None
|
|
22
|
+
deviceid: str | None = None
|
|
23
|
+
timestamp: datetime | None = None
|
|
24
|
+
traceback: str | None = Field(default=None, repr=False)
|
|
25
|
+
cleared: datetime | None = None
|
|
26
|
+
|
|
27
|
+
def model_post_init(self, _: Any) -> None:
|
|
28
|
+
if self.timestamp is None:
|
|
29
|
+
self.timestamp = datetime.now()
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def from_exception(cls, exc: Exception, **kwargs: Any) -> Self:
|
|
33
|
+
text = kwargs.pop("text", None) or str(exc)
|
|
34
|
+
return cls(text=text, traceback=cls.format_tb(exc.__traceback__), **kwargs)
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def format_tb(tback: TracebackType | None) -> str | None:
|
|
38
|
+
if tback is None:
|
|
39
|
+
return None
|
|
40
|
+
return "\n".join(traceback.format_tb(tback))
|
|
41
|
+
|
|
42
|
+
def clear(self, timestamp: datetime | None = None) -> None:
|
|
43
|
+
self.cleared = timestamp if timestamp is not None else datetime.now()
|
|
44
|
+
|
|
45
|
+
def __hash__(self) -> int:
|
|
46
|
+
return hash((self.text, self.matchid, self.cleared))
|
|
47
|
+
|
|
48
|
+
def __eq__(self, other: object) -> bool:
|
|
49
|
+
try:
|
|
50
|
+
other = cast(Alert, other)
|
|
51
|
+
return (self.text, self.matchid, self.cleared) == (
|
|
52
|
+
other.text,
|
|
53
|
+
other.matchid,
|
|
54
|
+
other.cleared,
|
|
55
|
+
)
|
|
56
|
+
except AttributeError:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
def __str__(self) -> str:
|
|
60
|
+
text = self.text
|
|
61
|
+
if self.detail:
|
|
62
|
+
text = f"{text}: {self.detail}"
|
|
63
|
+
|
|
64
|
+
ret = [text]
|
|
65
|
+
if self.matchid is not None:
|
|
66
|
+
ret.append(f"matchid={self.matchid}")
|
|
67
|
+
|
|
68
|
+
if self.deviceid is not None:
|
|
69
|
+
ret.append(f"deviceid={self.deviceid}")
|
|
70
|
+
|
|
71
|
+
return ", ".join(ret)
|
|
Binary file
|