webrockets 0.1.1__cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

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.
webrockets/__init__.py ADDED
@@ -0,0 +1,35 @@
1
+ # ruff: noqa: E402, I001
2
+ # Import order matters: webrockets must be imported first to avoid circular imports
3
+ from typing import Literal, TypedDict
4
+ from .webrockets import *
5
+ from .utils import noop
6
+ from .auth import BaseAuthentication
7
+
8
+
9
+ class _RedisBrokerConfigOptional(TypedDict, total=False):
10
+ url: str # default: "redis://localhost:6379"
11
+ channel: str # default: "ws_broadcast"
12
+
13
+
14
+ class RedisBrokerConfig(_RedisBrokerConfigOptional):
15
+ type: Literal["redis"]
16
+
17
+
18
+ class _AmqpBrokerConfigOptional(TypedDict, total=False):
19
+ url: str # default: "amqp://localhost:5672"
20
+ exchange: str # default: "ws_broadcast"
21
+ queue: str | None # default: auto-generated UUID
22
+ routing_key: str # default: "#"
23
+
24
+
25
+ class AmqpBrokerConfig(_AmqpBrokerConfigOptional):
26
+ type: Literal["amqp"]
27
+
28
+
29
+ BrokerConfig = RedisBrokerConfig | AmqpBrokerConfig
30
+
31
+ __all__ = ["noop", "BaseAuthentication", "BrokerConfig"]
32
+
33
+ __doc__ = webrockets.__doc__
34
+ if hasattr(webrockets, "__all__"):
35
+ __all__ += webrockets.__all__
@@ -0,0 +1,215 @@
1
+ from collections.abc import Callable, Coroutine, Iterable
2
+ from typing import (
3
+ TYPE_CHECKING,
4
+ Any,
5
+ Generic,
6
+ Literal,
7
+ TypedDict,
8
+ TypeVar,
9
+ overload,
10
+ )
11
+
12
+ from webrockets.auth import BaseAuthentication
13
+
14
+ if TYPE_CHECKING:
15
+ from pydantic import BaseModel
16
+
17
+ # Type aliases for Match parameters
18
+ MatchKey = str | Iterable[str]
19
+ MatchValue = str | int | Literal["*"] | Iterable[str | int | Literal["*"]]
20
+
21
+ class _RedisBrokerConfigOptional(TypedDict, total=False):
22
+ url: str # default: "redis://localhost:6379"
23
+ channel: str # default: "ws_broadcast"
24
+
25
+ class RedisBrokerConfig(_RedisBrokerConfigOptional):
26
+ type: Literal["redis"]
27
+
28
+ class _AmqpBrokerConfigOptional(TypedDict, total=False):
29
+ url: str # default: "amqp://localhost:5672"
30
+ exchange: str # default: "ws_broadcast"
31
+ queue: str | None # default: auto-generated UUID
32
+ routing_key: str # default: "#"
33
+
34
+ class AmqpBrokerConfig(_AmqpBrokerConfigOptional):
35
+ type: Literal["amqp"]
36
+
37
+ BrokerConfig = RedisBrokerConfig | AmqpBrokerConfig
38
+
39
+ class BaseConnection:
40
+ path: str
41
+ query_string: str
42
+ headers: dict[str, str]
43
+ cookies: dict[str, str]
44
+ user: Any | None
45
+
46
+ def __init__(
47
+ self,
48
+ path: str,
49
+ query_string: str,
50
+ headers: dict[str, str],
51
+ cookies: dict[str, str],
52
+ ) -> None: ...
53
+ def get_cookie(self, name: str) -> str | None: ...
54
+ def get_header(self, name: str) -> str | None: ...
55
+
56
+ class IncomingConnection(BaseConnection): ...
57
+
58
+ class Connection(BaseConnection):
59
+ def send(self, msg: str | bytes) -> None: ...
60
+ async def asend(self, msg: str | bytes) -> None: ...
61
+ def close(self, code: int = 1000, reason: str = "") -> None: ...
62
+ async def aclose(self, code: int = 1000, reason: str = "") -> None: ...
63
+ def broadcast(
64
+ self, groups: list[str], msg: str | bytes, exclude_self: bool = False
65
+ ) -> None: ...
66
+ async def abroadcast(
67
+ self, groups: list[str], msg: str | bytes, exclude_self: bool = False
68
+ ) -> None: ...
69
+ def join(self, group: str) -> bool:
70
+ """Join a group dynamically. Returns True if newly joined, False if already a member."""
71
+ ...
72
+ def leave(self, group: str) -> bool:
73
+ """Leave a group dynamically. Returns True if was a member, False otherwise."""
74
+ ...
75
+ def groups(self) -> list[str]:
76
+ """Get all groups this connection belongs to."""
77
+ ...
78
+ def group_size(self, group: str) -> int:
79
+ """Get the number of connections in a group."""
80
+ ...
81
+
82
+ T_Connection = TypeVar("T_Connection", bound=IncomingConnection | Connection)
83
+ T_Schema = TypeVar("T_Schema", bound="BaseModel" | str)
84
+
85
+ class ConnectDecorator(Generic[T_Connection]):
86
+ def __call__(
87
+ self, func: Callable[[T_Connection], None | Coroutine[Any, Any, None]]
88
+ ) -> Callable[[T_Connection], None | Coroutine[Any, Any, None]]: ...
89
+
90
+ class ReceiveDecorator(Generic[T_Schema]):
91
+ def __call__(
92
+ self, func: Callable[[Connection, T_Schema], None | Coroutine[Any, Any, None]]
93
+ ) -> Callable[[Connection, T_Schema], None | Coroutine[Any, Any, None]]: ...
94
+
95
+ class Match:
96
+ """
97
+ Pattern matcher for receive handlers.
98
+
99
+ Supports matching on JSON message fields with:
100
+ - Single or multiple keys: Match("type", ...) or Match(["type", "action"], ...)
101
+ - Single or multiple values: Match(..., "message") or Match(..., ["msg", "notify"])
102
+ - Integer values: Match("code", 1) or Match("code", [1, 2, 3])
103
+ - Wildcard matching: Match("type", "*") matches any value
104
+ - Mixed types: Match("type", ["message", 1, "*"])
105
+
106
+ Args:
107
+ key: Field name(s) to match on (string or iterable of strings)
108
+ value: Value(s) to match (string, int, "*" for wildcard, or iterable)
109
+ remove_key: If True, removes the matched key from the JSON before passing to handler
110
+ """
111
+
112
+ key: list[str]
113
+ value: list[str | int]
114
+ remove_key: bool
115
+
116
+ def __init__(
117
+ self,
118
+ key: MatchKey,
119
+ value: MatchValue,
120
+ *,
121
+ remove_key: bool = False,
122
+ ) -> None: ...
123
+
124
+ class WebsocketRoute:
125
+ path: str
126
+ default_group: str | None
127
+ authentication_classes: list[BaseAuthentication] | None = None
128
+
129
+ @overload
130
+ def connect(
131
+ self,
132
+ when: Literal["before"],
133
+ ) -> ConnectDecorator[IncomingConnection]: ...
134
+ @overload
135
+ def connect(
136
+ self,
137
+ when: Literal["after"],
138
+ ) -> ConnectDecorator[Connection]: ...
139
+ @overload
140
+ def receive(
141
+ self,
142
+ func: Callable[[Connection, str | bytes], None | Coroutine[Any, Any, None]],
143
+ /,
144
+ ) -> Callable[[Connection, str | bytes], None | Coroutine[Any, Any, None]]: ...
145
+ @overload
146
+ def receive(
147
+ self,
148
+ /,
149
+ match: Match,
150
+ ) -> ReceiveDecorator[str]: ...
151
+ @overload
152
+ def receive(
153
+ self,
154
+ /,
155
+ match: Match,
156
+ schema: type[T_Schema],
157
+ ) -> ReceiveDecorator[T_Schema]: ...
158
+ def disconnect(
159
+ self,
160
+ func: Callable[[Connection, int | None, str | None], None | Coroutine[Any, Any, None]],
161
+ ) -> Callable[[Connection, int | None, str | None], None | Coroutine[Any, Any, None]]: ...
162
+
163
+ class WebsocketServer:
164
+ def __init__(
165
+ self,
166
+ host: str = "0.0.0.0",
167
+ port: int = 46290,
168
+ broker: BrokerConfig | None = None,
169
+ ) -> None: ...
170
+ def start(self) -> None: ...
171
+ def stop(self) -> None: ...
172
+ def create_route(
173
+ self,
174
+ path: str,
175
+ default_group: str | None = None,
176
+ authentication_classes: list[BaseAuthentication] | None = None,
177
+ ) -> WebsocketRoute: ...
178
+ def addr(self) -> str: ...
179
+
180
+ # Broker functions for external broadcasting (e.g., from Django views, Celery tasks)
181
+ def setup_broadcast(config: BrokerConfig) -> None:
182
+ """
183
+ Initialize the broadcaster with a broker configuration.
184
+ Must be called before using broadcast() or abroadcast().
185
+ """
186
+ ...
187
+
188
+ def reset_broadcast() -> None:
189
+ """
190
+ Reset the broadcaster, allowing setup_broadcast() to be called again.
191
+ Primarily useful for testing when switching between broker configurations.
192
+ """
193
+ ...
194
+
195
+ def broadcast(groups: list[str], message: str) -> None:
196
+ """
197
+ Publish a message to the specified groups via the configured broker.
198
+ This is a blocking call that publishes synchronously.
199
+
200
+ Args:
201
+ groups: List of group names to broadcast to
202
+ message: The message payload (typically JSON string)
203
+ """
204
+ ...
205
+
206
+ async def abroadcast(groups: list[str], message: str) -> None:
207
+ """
208
+ Async version of broadcast().
209
+ Publish a message to the specified groups via the configured broker.
210
+
211
+ Args:
212
+ groups: List of group names to broadcast to
213
+ message: The message payload (typically JSON string)
214
+ """
215
+ ...
webrockets/apps.py ADDED
@@ -0,0 +1,22 @@
1
+ import importlib
2
+
3
+ if importlib.util.find_spec("django") is None:
4
+ raise ImportError(
5
+ 'Django is required to use apps.py. Install with: pip install "webrockets[django]"'
6
+ )
7
+
8
+ from django.apps import AppConfig
9
+
10
+
11
+ class WsRsAppConfig(AppConfig):
12
+ name = "webrockets"
13
+ verbose_name = "webrockets"
14
+
15
+ def ready(self) -> None:
16
+ from django.conf import settings
17
+
18
+ broker_config = getattr(settings, "WEBSOCKET_BROKER", None)
19
+ if broker_config:
20
+ from webrockets import setup_broadcast
21
+
22
+ setup_broadcast(broker_config)
webrockets/auth.py ADDED
@@ -0,0 +1,50 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+ from webrockets import IncomingConnection
5
+
6
+
7
+ class AuthenticationFailed(Exception):
8
+ """
9
+ Exception raised when authentication fails.
10
+ """
11
+
12
+ def __init__(self, detail: str = "Authentication failed", close_code: int = 4003):
13
+ self.detail = detail
14
+ self.close_code = close_code
15
+ super().__init__(detail)
16
+
17
+
18
+ class BaseAuthentication(ABC):
19
+ """
20
+ Base class for WebSocket authentication.
21
+
22
+ All authentication classes should extend this class and implement
23
+ the `authenticate` method.
24
+
25
+ Example usage:
26
+ class TokenAuthentication(BaseAuthentication):
27
+ def authenticate(self, conn: IncommingConnection) -> Any:
28
+ token = conn.get_header("Authorization")
29
+ if not token:
30
+ raise AuthenticationFailed("No token provided")
31
+ user = validate_token(token)
32
+ return user
33
+ """
34
+
35
+ @abstractmethod
36
+ def authenticate(self, conn: IncomingConnection) -> Any | None:
37
+ """
38
+ Authenticate the WebSocket connection request.
39
+
40
+ Args:
41
+ conn: IncommingConnection containing headers, cookies, path, and query string.
42
+
43
+ Returns:
44
+ A user if authentication succeeds.
45
+ Return None to skip this authenticator and try the next one.
46
+
47
+ Raises:
48
+ AuthenticationFailed: If authentication explicitly fails.
49
+ """
50
+ pass
webrockets/auth.pyi ADDED
@@ -0,0 +1,13 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+ from webrockets import IncomingConnection
5
+
6
+ class AuthenticationFailed(Exception):
7
+ detail: str
8
+ close_code: int
9
+ def __init__(self, detail: str = "Authentication failed", close_code: int = 4003) -> None: ...
10
+
11
+ class BaseAuthentication(ABC):
12
+ @abstractmethod
13
+ def authenticate(self, conn: IncomingConnection) -> Any | None: ...
@@ -0,0 +1,33 @@
1
+ import importlib.util
2
+
3
+ from webrockets import WebsocketServer
4
+
5
+ if importlib.util.find_spec("django") is None:
6
+ raise ImportError(
7
+ 'Django is required for webrockets.django. Install with: pip install "webrockets[django]"'
8
+ )
9
+
10
+ from django.conf import settings
11
+
12
+ from .auth import (
13
+ AuthenticationFailed,
14
+ CookieTokenAuthentication,
15
+ HeaderTokenAuthentication,
16
+ QueryStringTokenAuthentication,
17
+ SessionAuthentication,
18
+ )
19
+
20
+ server = WebsocketServer(
21
+ getattr(settings, "WEBSOCKET_HOST", "0.0.0.0"),
22
+ getattr(settings, "WEBSOCKET_PORT", 46290),
23
+ getattr(settings, "WEBSOCKET_BROKER", None),
24
+ )
25
+
26
+ __all__ = [
27
+ "AuthenticationFailed",
28
+ "SessionAuthentication",
29
+ "CookieTokenAuthentication",
30
+ "HeaderTokenAuthentication",
31
+ "QueryStringTokenAuthentication",
32
+ "server",
33
+ ]
@@ -0,0 +1,3 @@
1
+ from webrockets import WebsocketServer
2
+
3
+ server: WebsocketServer
@@ -0,0 +1,181 @@
1
+ from types import SimpleNamespace
2
+ from typing import Any
3
+ from urllib.parse import parse_qs
4
+
5
+ from django.conf import settings
6
+ from django.contrib import auth
7
+ from django.utils.module_loading import import_string
8
+
9
+ from webrockets import IncomingConnection
10
+ from webrockets.auth import AuthenticationFailed, BaseAuthentication
11
+
12
+
13
+ class SessionAuthentication(BaseAuthentication):
14
+ def __init__(self, session_cookie_name: str | None = None):
15
+ """
16
+ Initialize SessionAuthentication.
17
+
18
+ Args:
19
+ session_cookie_name: Name of the session cookie. If None, uses
20
+ Django's SESSION_COOKIE_NAME setting.
21
+ """
22
+ self._session_cookie_name = session_cookie_name
23
+
24
+ @property
25
+ def session_cookie_name(self) -> str:
26
+ if self._session_cookie_name:
27
+ return self._session_cookie_name
28
+ return getattr(settings, "SESSION_COOKIE_NAME", "sessionid")
29
+
30
+ def _get_session_store(self):
31
+ engine = getattr(settings, "SESSION_ENGINE", "django.contrib.sessions.backends.db")
32
+ return import_string(f"{engine}.SessionStore")
33
+
34
+ def authenticate(self, conn: IncomingConnection) -> Any | None:
35
+ session_id = conn.get_cookie(self.session_cookie_name)
36
+ if not session_id:
37
+ raise AuthenticationFailed("No session cookie found", close_code=4001)
38
+
39
+ SessionStore = self._get_session_store()
40
+ session = SessionStore(session_key=session_id)
41
+
42
+ request = SimpleNamespace(session=session)
43
+ user = auth.get_user(request)
44
+ if not user.is_authenticated:
45
+ raise AuthenticationFailed("No authenticated user in session", close_code=4001)
46
+
47
+ return user
48
+
49
+
50
+ class CookieTokenAuthentication(BaseAuthentication):
51
+ """
52
+ Authentication class that reads a token from a cookie.
53
+
54
+ This is useful for JWT tokens stored in HTTP-only cookies.
55
+ Users must implement the `validate_token` method or provide a validator function.
56
+
57
+ Example:
58
+ class JWTCookieAuth(CookieTokenAuthentication):
59
+ cookie_name = "jwt_token"
60
+
61
+ def validate_token(self, token: str) -> Any:
62
+ payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
63
+ return User.objects.get(pk=payload["user_id"])
64
+ """
65
+
66
+ cookie_name: str = "auth_token"
67
+
68
+ def authenticate(self, conn: IncomingConnection) -> Any | None:
69
+ token = conn.get_cookie(self.cookie_name)
70
+ if not token:
71
+ raise AuthenticationFailed(f"No {self.cookie_name} cookie found", close_code=4001)
72
+
73
+ user = self.validate_token(token)
74
+ return user
75
+
76
+ def validate_token(self, token: str) -> Any:
77
+ """
78
+ Validate the token and return the user.
79
+
80
+ Override this method in subclasses to implement token validation.
81
+
82
+ Args:
83
+ token: The token string from the cookie.
84
+
85
+ Returns:
86
+ The authenticated user object.
87
+
88
+ Raises:
89
+ AuthenticationFailed: If the token is invalid.
90
+ """
91
+ raise NotImplementedError("Subclasses must implement validate_token()")
92
+
93
+
94
+ class HeaderTokenAuthentication(BaseAuthentication):
95
+ """
96
+ Authentication class that reads a token from an HTTP header.
97
+
98
+ Note: WebSocket connections from browsers cannot set custom headers
99
+ in the initial handshake. This is primarily useful for non-browser clients
100
+ or when using query parameters as a fallback.
101
+
102
+ For browser-based JWT authentication, consider:
103
+ - Using CookieTokenAuthentication with HTTP-only cookies
104
+ - Passing tokens via query string (QueryStringTokenAuthentication)
105
+ - Performing authentication after connection via message protocol
106
+
107
+ Example for non-browser clients:
108
+ class BearerTokenAuth(HeaderTokenAuthentication):
109
+ header_name = "authorization"
110
+ keyword = "Bearer"
111
+
112
+ def validate_token(self, token: str) -> Any:
113
+ return verify_and_get_user(token)
114
+ """
115
+
116
+ header_name: str = "authorization"
117
+ keyword: str = "Bearer"
118
+
119
+ def authenticate(self, conn: IncomingConnection) -> Any | None:
120
+ auth_header = conn.get_header(self.header_name)
121
+ if not auth_header:
122
+ raise AuthenticationFailed(f"No {self.header_name} header found", close_code=4001)
123
+
124
+ parts = auth_header.split()
125
+ if len(parts) != 2 or parts[0].lower() != self.keyword.lower():
126
+ raise AuthenticationFailed(f"Invalid {self.header_name} header format", close_code=4001)
127
+
128
+ token = parts[1]
129
+ user = self.validate_token(token)
130
+ return user
131
+
132
+ def validate_token(self, token: str) -> Any:
133
+ """
134
+ Validate the token and return the user.
135
+
136
+ Override this method in subclasses to implement token validation.
137
+ """
138
+ raise NotImplementedError("Subclasses must implement validate_token()")
139
+
140
+
141
+ class QueryStringTokenAuthentication(BaseAuthentication):
142
+ """
143
+ Authentication class that reads a token from the query string.
144
+
145
+ This is useful for browser-based WebSocket connections where custom headers
146
+ cannot be set. The token is passed as a URL parameter.
147
+
148
+ Example URL: ws://example.com/chat/?token=your-jwt-token
149
+
150
+ Note: Query string tokens may be logged in server access logs.
151
+ Consider using short-lived tokens for this authentication method.
152
+
153
+ Example:
154
+ class JWTQueryAuth(QueryStringTokenAuthentication):
155
+ query_param = "token"
156
+
157
+ def validate_token(self, token: str) -> Any:
158
+ payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
159
+ return User.objects.get(pk=payload["user_id"])
160
+ """
161
+
162
+ query_param: str = "token"
163
+
164
+ def authenticate(self, conn: IncomingConnection) -> Any | None:
165
+ params = parse_qs(conn.query_string)
166
+ tokens = params.get(self.query_param, [])
167
+
168
+ if not tokens:
169
+ raise AuthenticationFailed(f"No {self.query_param} in query string", close_code=4001)
170
+
171
+ token = tokens[0]
172
+ user = self.validate_token(token)
173
+ return user
174
+
175
+ def validate_token(self, token: str) -> Any:
176
+ """
177
+ Validate the token and return the user.
178
+
179
+ Override this method in subclasses to implement token validation.
180
+ """
181
+ raise NotImplementedError("Subclasses must implement validate_token()")
@@ -0,0 +1,26 @@
1
+ from typing import Any
2
+
3
+ from webrockets import IncomingConnection
4
+ from webrockets.auth import BaseAuthentication
5
+
6
+ class SessionAuthentication(BaseAuthentication):
7
+ def __init__(self, session_cookie_name: str | None = None) -> None: ...
8
+ @property
9
+ def session_cookie_name(self) -> str: ...
10
+ def authenticate(self, conn: IncomingConnection) -> Any | None: ...
11
+
12
+ class CookieTokenAuthentication(BaseAuthentication):
13
+ cookie_name: str
14
+ def authenticate(self, conn: IncomingConnection) -> Any | None: ...
15
+ def validate_token(self, token: str) -> Any: ...
16
+
17
+ class HeaderTokenAuthentication(BaseAuthentication):
18
+ header_name: str
19
+ keyword: str
20
+ def authenticate(self, conn: IncomingConnection) -> Any | None: ...
21
+ def validate_token(self, token: str) -> Any: ...
22
+
23
+ class QueryStringTokenAuthentication(BaseAuthentication):
24
+ query_param: str
25
+ def authenticate(self, conn: IncomingConnection) -> Any | None: ...
26
+ def validate_token(self, token: str) -> Any: ...
File without changes
File without changes
@@ -0,0 +1,49 @@
1
+ import importlib
2
+ import logging
3
+
4
+ if importlib.util.find_spec("django") is None:
5
+ raise ImportError(
6
+ 'Django is required to use runwebsockets command. Install with: pip install "webrockets[django]"'
7
+ )
8
+
9
+ from django.core.management.base import BaseCommand
10
+ from django.utils.module_loading import autodiscover_modules
11
+
12
+ from webrockets.django import server
13
+
14
+ LOG_LEVELS = {
15
+ "debug": logging.DEBUG,
16
+ "info": logging.INFO,
17
+ "warning": logging.WARNING,
18
+ "error": logging.ERROR,
19
+ "critical": logging.CRITICAL,
20
+ }
21
+
22
+
23
+ class Command(BaseCommand):
24
+ help = "Start the webrockets WebSocket server"
25
+
26
+ def add_arguments(self, parser):
27
+ parser.add_argument(
28
+ "--log-level",
29
+ type=str,
30
+ choices=LOG_LEVELS.keys(),
31
+ default=None,
32
+ help="Set the log level for webrockets (debug, info, warning, error, critical)",
33
+ )
34
+
35
+ def handle(self, *args, **options):
36
+ if options["log_level"]:
37
+ level = LOG_LEVELS[options["log_level"]]
38
+ logger = logging.getLogger("webrockets")
39
+ logger.setLevel(level)
40
+ if not logger.handlers:
41
+ handler = logging.StreamHandler()
42
+ handler.setFormatter(
43
+ logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
44
+ )
45
+ logger.addHandler(handler)
46
+ logger.propagate = False
47
+
48
+ autodiscover_modules("websockets", "sockets", "sse", "views")
49
+ server.start()
webrockets/py.typed ADDED
File without changes
webrockets/utils.py ADDED
@@ -0,0 +1,2 @@
1
+ async def noop():
2
+ pass
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: webrockets
3
+ Version: 0.1.1
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
7
+ Requires-Dist: django>=4.2 ; extra == 'django'
8
+ Requires-Dist: pydantic>=2.0 ; extra == 'schema'
9
+ Provides-Extra: django
10
+ Provides-Extra: schema
11
+ License-File: LICENSE.md
12
+ Requires-Python: >=3.10
@@ -0,0 +1,19 @@
1
+ webrockets/__init__.py,sha256=yVVqsX_32sccg7F0iLxN-8_kT7Bn_D98oa5-8R-HXUA,1004
2
+ webrockets/__init__.pyi,sha256=t6278tRPmbYEaF6NXGv9itLsb-1IuLiGxISFl1txxkY,6766
3
+ webrockets/apps.py,sha256=ba3I6aEq7UYUW5JeeJaWKEAUFRPOJE2zZtxQ3UHuZxs,572
4
+ webrockets/auth.py,sha256=uWoHreBSMIjyZNLElxdCwBgoNDJLnbzecrtqqfmCbG4,1475
5
+ webrockets/auth.pyi,sha256=cDFtZ-YPmMmG04NRuA_3Ysr_ZO3NorRwLemtXzIr_3A,401
6
+ webrockets/django/__init__.py,sha256=dKYx6rWpGVVywVKRbLA5gbViAPq6526ueNW6QkxsOBc,814
7
+ webrockets/django/__init__.pyi,sha256=YkvLn1DouBIUrwgmOnFtFCExA0SkZBfuQAllXouTOfk,64
8
+ webrockets/django/auth.py,sha256=RYuoFS6E4cKrsq2IkwcgzGTw6NiNgGMURAt8NyWHM6w,6358
9
+ webrockets/django/auth.pyi,sha256=xfzqc3705P3bXshhiUjhU_TMV5Kk01uAy4VYvQUPXPc,993
10
+ webrockets/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ webrockets/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ webrockets/management/commands/runwebsockets.py,sha256=9VvxiWBl_o-AA5xBjphWYR0BJPMJDhf8ll_-l4BXZtA,1522
13
+ webrockets/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ webrockets/utils.py,sha256=QgvZ19wfhim8gRHIyZ62U031lchLmvh0r6JSa1VMmso,27
15
+ webrockets/webrockets.cpython-310-aarch64-linux-gnu.so,sha256=sJ5VaT1K8FiZxMnQ7RVPUWvxZw_nztBcvv4pCCl7NQ4,4081456
16
+ webrockets-0.1.1.dist-info/METADATA,sha256=n8uz2WZJdSeLWER6V6UsISR0txs_-l3EuWBtkEbtkjk,427
17
+ webrockets-0.1.1.dist-info/WHEEL,sha256=RB6BklbQly-nkZ92dlo_PD8Q-04KsWFjgp4lJ2wl33k,149
18
+ webrockets-0.1.1.dist-info/licenses/LICENSE.md,sha256=enjfNwKVg0wUKgkHasdFANY3j36T-Coc71PwEzvvjoI,1076
19
+ webrockets-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.11.5)
3
+ Root-Is-Purelib: false
4
+ Tag: cp310-cp310-manylinux_2_17_aarch64
5
+ Tag: cp310-cp310-manylinux2014_aarch64
@@ -0,0 +1,8 @@
1
+ Copyright (c) 2026 Konstantinos Artopoulos
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+