varco-ws 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.
- varco_ws-0.1.0/.gitignore +209 -0
- varco_ws-0.1.0/PKG-INFO +20 -0
- varco_ws-0.1.0/README.md +0 -0
- varco_ws-0.1.0/pyproject.toml +49 -0
- varco_ws-0.1.0/tests/__init__.py +0 -0
- varco_ws-0.1.0/tests/test_di.py +261 -0
- varco_ws-0.1.0/tests/test_ws_bus.py +372 -0
- varco_ws-0.1.0/varco_ws/__init__.py +44 -0
- varco_ws-0.1.0/varco_ws/di.py +276 -0
- varco_ws-0.1.0/varco_ws/py.typed +0 -0
- varco_ws-0.1.0/varco_ws/sse.py +417 -0
- varco_ws-0.1.0/varco_ws/websocket.py +431 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
.python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
|
|
157
|
+
# mypy
|
|
158
|
+
.mypy_cache/
|
|
159
|
+
.dmypy.json
|
|
160
|
+
dmypy.json
|
|
161
|
+
|
|
162
|
+
# Pyre type checker
|
|
163
|
+
.pyre/
|
|
164
|
+
|
|
165
|
+
# pytype static type analyzer
|
|
166
|
+
.pytype/
|
|
167
|
+
|
|
168
|
+
# Cython debug symbols
|
|
169
|
+
cython_debug/
|
|
170
|
+
|
|
171
|
+
# PyCharm
|
|
172
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
173
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
174
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
175
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
176
|
+
#.idea/
|
|
177
|
+
|
|
178
|
+
# Abstra
|
|
179
|
+
# Abstra is an AI-powered process automation framework.
|
|
180
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
181
|
+
# Learn more at https://abstra.io/docs
|
|
182
|
+
.abstra/
|
|
183
|
+
|
|
184
|
+
# Visual Studio Code
|
|
185
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
186
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
188
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
189
|
+
# .vscode/
|
|
190
|
+
|
|
191
|
+
# Ruff stuff:
|
|
192
|
+
.ruff_cache/
|
|
193
|
+
|
|
194
|
+
# PyPI configuration file
|
|
195
|
+
.pypirc
|
|
196
|
+
|
|
197
|
+
# Cursor
|
|
198
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
199
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
200
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
201
|
+
.cursorignore
|
|
202
|
+
.cursorindexingignore
|
|
203
|
+
|
|
204
|
+
todo*.txt
|
|
205
|
+
|
|
206
|
+
# Marimo
|
|
207
|
+
marimo/_static/
|
|
208
|
+
marimo/_lsp/
|
|
209
|
+
__marimo__/
|
varco_ws-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: varco-ws
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: WebSocket and SSE event bus backends for varco — real-time browser push
|
|
5
|
+
Project-URL: Homepage, https://github.com/edoardoscarpaci/varco
|
|
6
|
+
Project-URL: Repository, https://github.com/edoardoscarpaci/varco/tree/main/varco_ws
|
|
7
|
+
Project-URL: Issues, https://github.com/edoardoscarpaci/varco/issues
|
|
8
|
+
Author-email: "edoardo.scarpaci" <edoardo.scarpaci@gmail.com>
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
Keywords: async,domain-events,event-bus,fastapi,sse,varco,websocket
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: varco-core
|
varco_ws-0.1.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "varco-ws"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "WebSocket and SSE event bus backends for varco — real-time browser push"
|
|
5
|
+
authors = [{ name = "edoardo.scarpaci", email = "edoardo.scarpaci@gmail.com" }]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = { text = "Apache-2.0" }
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
keywords = ["fastapi", "websocket", "sse", "async", "event-bus", "domain-events", "varco"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: Apache Software License",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
17
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
# Workspace sibling — resolved from the monorepo via [tool.uv.sources] below.
|
|
22
|
+
"varco-core",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/edoardoscarpaci/varco"
|
|
27
|
+
Repository = "https://github.com/edoardoscarpaci/varco/tree/main/varco_ws"
|
|
28
|
+
Issues = "https://github.com/edoardoscarpaci/varco/issues"
|
|
29
|
+
|
|
30
|
+
[tool.uv.sources]
|
|
31
|
+
# Resolve varco-core from the workspace instead of PyPI during development.
|
|
32
|
+
varco-core = { workspace = true }
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=8.0",
|
|
37
|
+
"pytest-asyncio>=0.24",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["hatchling"]
|
|
42
|
+
build-backend = "hatchling.build"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["varco_ws"]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
asyncio_mode = "auto"
|
|
49
|
+
testpaths = ["tests"]
|
|
File without changes
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tests.test_di
|
|
3
|
+
=============
|
|
4
|
+
Unit tests for varco_ws.di — WebSocketConfiguration and SSEConfiguration.
|
|
5
|
+
|
|
6
|
+
Covers:
|
|
7
|
+
WebSocketConfiguration — provides a WebSocketEventBus singleton that
|
|
8
|
+
wraps the container's AbstractEventBus.
|
|
9
|
+
SSEConfiguration — provides an SSEEventBus singleton that wraps
|
|
10
|
+
the container's AbstractEventBus.
|
|
11
|
+
varco_ws public __init__ — DI classes are exported at the package level.
|
|
12
|
+
|
|
13
|
+
All tests use InMemoryEventBus — no real broker required.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
from varco_core.event.base import AbstractEventBus
|
|
21
|
+
from varco_core.event.memory import InMemoryEventBus
|
|
22
|
+
|
|
23
|
+
from varco_ws import SSEConfiguration, WebSocketConfiguration
|
|
24
|
+
from varco_ws.di import SSEConfiguration as SSEConfigurationDirect
|
|
25
|
+
from varco_ws.di import WebSocketConfiguration as WebSocketConfigurationDirect
|
|
26
|
+
from varco_ws.sse import SSEEventBus
|
|
27
|
+
from varco_ws.websocket import WebSocketEventBus
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ── Helpers ────────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _make_container_with_bus() -> tuple[object, InMemoryEventBus]:
|
|
34
|
+
"""
|
|
35
|
+
Build a minimal providify DIContainer with an InMemoryEventBus registered
|
|
36
|
+
as AbstractEventBus. Returns (container, bus).
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
from providify import DIContainer
|
|
40
|
+
except ImportError:
|
|
41
|
+
pytest.skip("providify not installed — skipping DI tests")
|
|
42
|
+
|
|
43
|
+
container = DIContainer()
|
|
44
|
+
bus = InMemoryEventBus()
|
|
45
|
+
# Register InMemoryEventBus as the AbstractEventBus implementation.
|
|
46
|
+
container.provide(lambda: bus, AbstractEventBus)
|
|
47
|
+
return container, bus
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ── __init__ re-export tests ───────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_websocket_configuration_exported_from_init() -> None:
|
|
54
|
+
"""
|
|
55
|
+
WebSocketConfiguration must be importable from the top-level varco_ws package.
|
|
56
|
+
This guards against accidental removal from __init__.py.
|
|
57
|
+
"""
|
|
58
|
+
assert WebSocketConfiguration is WebSocketConfigurationDirect
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_sse_configuration_exported_from_init() -> None:
|
|
62
|
+
"""
|
|
63
|
+
SSEConfiguration must be importable from the top-level varco_ws package.
|
|
64
|
+
"""
|
|
65
|
+
assert SSEConfiguration is SSEConfigurationDirect
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ── WebSocketConfiguration tests ──────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_websocket_configuration_provides_websocket_event_bus() -> None:
|
|
72
|
+
"""
|
|
73
|
+
After installing WebSocketConfiguration, the container must resolve
|
|
74
|
+
WebSocketEventBus.
|
|
75
|
+
"""
|
|
76
|
+
container, _ = _make_container_with_bus()
|
|
77
|
+
container.install(WebSocketConfiguration)
|
|
78
|
+
|
|
79
|
+
ws_bus = container.get(WebSocketEventBus)
|
|
80
|
+
assert isinstance(ws_bus, WebSocketEventBus)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_websocket_configuration_wraps_registered_bus() -> None:
|
|
84
|
+
"""
|
|
85
|
+
The WebSocketEventBus provided by WebSocketConfiguration must wrap
|
|
86
|
+
the AbstractEventBus that was registered in the container.
|
|
87
|
+
"""
|
|
88
|
+
container, bus = _make_container_with_bus()
|
|
89
|
+
container.install(WebSocketConfiguration)
|
|
90
|
+
|
|
91
|
+
ws_bus = container.get(WebSocketEventBus)
|
|
92
|
+
# Internal attribute _bus must be the registered InMemoryEventBus.
|
|
93
|
+
assert ws_bus._bus is bus
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_websocket_configuration_singleton() -> None:
|
|
97
|
+
"""
|
|
98
|
+
WebSocketEventBus must be a singleton — resolving it twice returns the
|
|
99
|
+
same instance.
|
|
100
|
+
"""
|
|
101
|
+
container, _ = _make_container_with_bus()
|
|
102
|
+
container.install(WebSocketConfiguration)
|
|
103
|
+
|
|
104
|
+
first = container.get(WebSocketEventBus)
|
|
105
|
+
second = container.get(WebSocketEventBus)
|
|
106
|
+
assert first is second
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_websocket_configuration_bus_not_started_after_install() -> None:
|
|
110
|
+
"""
|
|
111
|
+
The WebSocketEventBus must NOT be started automatically by the DI module.
|
|
112
|
+
Callers must call start() explicitly in their lifespan handler.
|
|
113
|
+
|
|
114
|
+
DESIGN: not starting automatically avoids an asyncio.Loop dependency at
|
|
115
|
+
installation time — the container may be built synchronously before an
|
|
116
|
+
event loop is running.
|
|
117
|
+
"""
|
|
118
|
+
container, _ = _make_container_with_bus()
|
|
119
|
+
container.install(WebSocketConfiguration)
|
|
120
|
+
|
|
121
|
+
ws_bus = container.get(WebSocketEventBus)
|
|
122
|
+
# Internal subscription handle is None when not started.
|
|
123
|
+
assert ws_bus._subscription is None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ── SSEConfiguration tests ────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_sse_configuration_provides_sse_event_bus() -> None:
|
|
130
|
+
"""
|
|
131
|
+
After installing SSEConfiguration, the container must resolve SSEEventBus.
|
|
132
|
+
"""
|
|
133
|
+
container, _ = _make_container_with_bus()
|
|
134
|
+
container.install(SSEConfiguration)
|
|
135
|
+
|
|
136
|
+
sse_bus = container.get(SSEEventBus)
|
|
137
|
+
assert isinstance(sse_bus, SSEEventBus)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_sse_configuration_wraps_registered_bus() -> None:
|
|
141
|
+
"""
|
|
142
|
+
The SSEEventBus provided by SSEConfiguration must wrap the
|
|
143
|
+
AbstractEventBus that was registered in the container.
|
|
144
|
+
"""
|
|
145
|
+
container, bus = _make_container_with_bus()
|
|
146
|
+
container.install(SSEConfiguration)
|
|
147
|
+
|
|
148
|
+
sse_bus = container.get(SSEEventBus)
|
|
149
|
+
assert sse_bus._bus is bus
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_sse_configuration_singleton() -> None:
|
|
153
|
+
"""SSEEventBus must be a singleton."""
|
|
154
|
+
container, _ = _make_container_with_bus()
|
|
155
|
+
container.install(SSEConfiguration)
|
|
156
|
+
|
|
157
|
+
first = container.get(SSEEventBus)
|
|
158
|
+
second = container.get(SSEEventBus)
|
|
159
|
+
assert first is second
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_sse_configuration_bus_not_started_after_install() -> None:
|
|
163
|
+
"""
|
|
164
|
+
The SSEEventBus must NOT be started automatically. Callers start it
|
|
165
|
+
in the FastAPI lifespan handler.
|
|
166
|
+
"""
|
|
167
|
+
container, _ = _make_container_with_bus()
|
|
168
|
+
container.install(SSEConfiguration)
|
|
169
|
+
|
|
170
|
+
sse_bus = container.get(SSEEventBus)
|
|
171
|
+
assert sse_bus._subscription is None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ── Combined WS + SSE tests ───────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_both_configurations_can_be_installed_together() -> None:
|
|
178
|
+
"""
|
|
179
|
+
Installing both WebSocketConfiguration and SSEConfiguration in the same
|
|
180
|
+
container must not conflict — they provide different types.
|
|
181
|
+
"""
|
|
182
|
+
container, _ = _make_container_with_bus()
|
|
183
|
+
container.install(WebSocketConfiguration)
|
|
184
|
+
container.install(SSEConfiguration)
|
|
185
|
+
|
|
186
|
+
ws_bus = container.get(WebSocketEventBus)
|
|
187
|
+
sse_bus = container.get(SSEEventBus)
|
|
188
|
+
|
|
189
|
+
# Different types — different instances.
|
|
190
|
+
assert isinstance(ws_bus, WebSocketEventBus)
|
|
191
|
+
assert isinstance(sse_bus, SSEEventBus)
|
|
192
|
+
assert ws_bus is not sse_bus
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
async def test_ws_bus_is_functional_after_start() -> None:
|
|
196
|
+
"""
|
|
197
|
+
The WebSocketEventBus obtained from the container must be fully functional
|
|
198
|
+
after manually calling start().
|
|
199
|
+
|
|
200
|
+
DESIGN: functional test to confirm the DI-provided adapter delivers events,
|
|
201
|
+
not just that it was constructed correctly.
|
|
202
|
+
"""
|
|
203
|
+
import asyncio
|
|
204
|
+
|
|
205
|
+
from varco_core.event.base import Event
|
|
206
|
+
|
|
207
|
+
class PingEvent(Event):
|
|
208
|
+
__event_type__ = "test.ping"
|
|
209
|
+
count: int = 0
|
|
210
|
+
|
|
211
|
+
container, bus = _make_container_with_bus()
|
|
212
|
+
container.install(WebSocketConfiguration)
|
|
213
|
+
|
|
214
|
+
ws_bus = container.get(WebSocketEventBus)
|
|
215
|
+
await ws_bus.start()
|
|
216
|
+
|
|
217
|
+
class FakeWebSocket:
|
|
218
|
+
sent: list[str] = []
|
|
219
|
+
|
|
220
|
+
async def send_text(self, msg: str) -> None:
|
|
221
|
+
self.sent.append(msg)
|
|
222
|
+
|
|
223
|
+
fake_ws = FakeWebSocket()
|
|
224
|
+
async with ws_bus.connect(fake_ws):
|
|
225
|
+
await bus.publish(PingEvent(count=1))
|
|
226
|
+
await asyncio.sleep(0)
|
|
227
|
+
await asyncio.sleep(0)
|
|
228
|
+
|
|
229
|
+
assert len(fake_ws.sent) == 1
|
|
230
|
+
|
|
231
|
+
await ws_bus.stop()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
async def test_sse_bus_is_functional_after_start() -> None:
|
|
235
|
+
"""
|
|
236
|
+
The SSEEventBus obtained from the container must be fully functional
|
|
237
|
+
after manually calling start().
|
|
238
|
+
"""
|
|
239
|
+
import asyncio
|
|
240
|
+
|
|
241
|
+
from varco_core.event.base import Event
|
|
242
|
+
|
|
243
|
+
class PongEvent(Event):
|
|
244
|
+
__event_type__ = "test.pong"
|
|
245
|
+
value: str = ""
|
|
246
|
+
|
|
247
|
+
container, bus = _make_container_with_bus()
|
|
248
|
+
container.install(SSEConfiguration)
|
|
249
|
+
|
|
250
|
+
sse_bus = container.get(SSEEventBus)
|
|
251
|
+
await sse_bus.start()
|
|
252
|
+
|
|
253
|
+
async with sse_bus.subscribe() as conn:
|
|
254
|
+
await bus.publish(PongEvent(value="hello"))
|
|
255
|
+
await asyncio.sleep(0)
|
|
256
|
+
|
|
257
|
+
message = await asyncio.wait_for(conn._queue.get(), timeout=1.0)
|
|
258
|
+
assert "test.pong" in message
|
|
259
|
+
assert "hello" in message
|
|
260
|
+
|
|
261
|
+
await sse_bus.stop()
|