jsocket 1.9.5__py3-none-any.whl → 1.9.6__py3-none-any.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.
- jsocket/__init__.py +1 -0
- jsocket/_version.py +3 -0
- jsocket/jsocket_base.py +4 -2
- jsocket/tserver.py +95 -7
- jsocket-1.9.6.dist-info/METADATA +166 -0
- jsocket-1.9.6.dist-info/RECORD +9 -0
- {jsocket-1.9.5.dist-info → jsocket-1.9.6.dist-info}/WHEEL +1 -1
- jsocket-1.9.5.dist-info/METADATA +0 -27
- jsocket-1.9.5.dist-info/RECORD +0 -8
- {jsocket-1.9.5.dist-info → jsocket-1.9.6.dist-info/licenses}/LICENSE +0 -0
- {jsocket-1.9.5.dist-info → jsocket-1.9.6.dist-info}/top_level.txt +0 -0
jsocket/__init__.py
CHANGED
jsocket/_version.py
ADDED
jsocket/jsocket_base.py
CHANGED
|
@@ -19,14 +19,14 @@ __copyright__= """
|
|
|
19
19
|
See the License for the specific language governing permissions and
|
|
20
20
|
limitations under the License.
|
|
21
21
|
"""
|
|
22
|
-
__version__ = "1.0.3"
|
|
23
|
-
|
|
24
22
|
import json
|
|
25
23
|
import socket
|
|
26
24
|
import struct
|
|
27
25
|
import logging
|
|
28
26
|
import time
|
|
29
27
|
|
|
28
|
+
from ._version import __version__
|
|
29
|
+
|
|
30
30
|
logger = logging.getLogger("jsocket")
|
|
31
31
|
|
|
32
32
|
|
|
@@ -46,6 +46,7 @@ class JsonSocket:
|
|
|
46
46
|
self._timeout = timeout
|
|
47
47
|
self._address = address
|
|
48
48
|
self._port = port
|
|
49
|
+
self._last_client_addr = None
|
|
49
50
|
# Ensure the primary socket respects timeout for accept/connect operations
|
|
50
51
|
self.socket.settimeout(self._timeout)
|
|
51
52
|
|
|
@@ -183,6 +184,7 @@ class JsonServer(JsonSocket):
|
|
|
183
184
|
"""Listen and accept a single client connection; set timeout accordingly."""
|
|
184
185
|
self._listen()
|
|
185
186
|
self.conn, addr = self._accept()
|
|
187
|
+
self._last_client_addr = addr
|
|
186
188
|
self.conn.settimeout(self.timeout)
|
|
187
189
|
logger.debug(
|
|
188
190
|
"connection accepted, conn socket (%s,%d,%s)", addr[0], addr[1], str(self.conn.gettimeout())
|
jsocket/tserver.py
CHANGED
|
@@ -20,15 +20,15 @@ __copyright__= """
|
|
|
20
20
|
See the License for the specific language governing permissions and
|
|
21
21
|
limitations under the License.
|
|
22
22
|
"""
|
|
23
|
-
__version__ = "1.0.3"
|
|
24
|
-
|
|
25
23
|
import threading
|
|
26
24
|
import socket
|
|
27
25
|
import time
|
|
28
26
|
import logging
|
|
29
27
|
import abc
|
|
30
28
|
from typing import Optional
|
|
29
|
+
|
|
31
30
|
from jsocket import jsocket_base
|
|
31
|
+
from ._version import __version__
|
|
32
32
|
|
|
33
33
|
logger = logging.getLogger("jsocket.tserver")
|
|
34
34
|
|
|
@@ -41,6 +41,16 @@ def _response_summary(resp_obj) -> str:
|
|
|
41
41
|
return f"type={type(resp_obj).__name__}"
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def _format_client_id(addr) -> str:
|
|
45
|
+
try:
|
|
46
|
+
host, port = addr[0], addr[1]
|
|
47
|
+
if ":" in host:
|
|
48
|
+
return f"[{host}]:{port}"
|
|
49
|
+
return f"{host}:{port}"
|
|
50
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
51
|
+
return "unknown"
|
|
52
|
+
|
|
53
|
+
|
|
44
54
|
class ThreadedServer(threading.Thread, jsocket_base.JsonServer, metaclass=abc.ABCMeta):
|
|
45
55
|
"""Single-threaded server that accepts one connection and processes messages in its thread."""
|
|
46
56
|
|
|
@@ -48,6 +58,9 @@ class ThreadedServer(threading.Thread, jsocket_base.JsonServer, metaclass=abc.AB
|
|
|
48
58
|
threading.Thread.__init__(self)
|
|
49
59
|
jsocket_base.JsonServer.__init__(self, **kwargs)
|
|
50
60
|
self._is_alive = False
|
|
61
|
+
self._stats_lock = threading.Lock()
|
|
62
|
+
self._client_started_at = None
|
|
63
|
+
self._client_id = None
|
|
51
64
|
|
|
52
65
|
@abc.abstractmethod
|
|
53
66
|
def _process_message(self, obj) -> Optional[dict]:
|
|
@@ -61,6 +74,32 @@ class ThreadedServer(threading.Thread, jsocket_base.JsonServer, metaclass=abc.AB
|
|
|
61
74
|
# Return None in the base class to satisfy linters; subclasses should override.
|
|
62
75
|
return None
|
|
63
76
|
|
|
77
|
+
def _record_client_start(self):
|
|
78
|
+
addr = getattr(self, "_last_client_addr", None)
|
|
79
|
+
if addr is None:
|
|
80
|
+
try:
|
|
81
|
+
addr = self.conn.getpeername()
|
|
82
|
+
except OSError:
|
|
83
|
+
addr = None
|
|
84
|
+
with self._stats_lock:
|
|
85
|
+
self._client_started_at = time.monotonic()
|
|
86
|
+
self._client_id = _format_client_id(addr)
|
|
87
|
+
|
|
88
|
+
def _clear_client_stats(self):
|
|
89
|
+
with self._stats_lock:
|
|
90
|
+
self._client_started_at = None
|
|
91
|
+
self._client_id = None
|
|
92
|
+
|
|
93
|
+
def get_client_stats(self) -> dict:
|
|
94
|
+
"""Return connected client count and per-client durations in seconds."""
|
|
95
|
+
with self._stats_lock:
|
|
96
|
+
started_at = self._client_started_at
|
|
97
|
+
client_id = self._client_id
|
|
98
|
+
if not started_at or not client_id or not self.connected:
|
|
99
|
+
return {"connected_clients": 0, "clients": {}}
|
|
100
|
+
duration = time.monotonic() - started_at
|
|
101
|
+
return {"connected_clients": 1, "clients": {client_id: duration}}
|
|
102
|
+
|
|
64
103
|
def _accept_client(self) -> bool:
|
|
65
104
|
"""Accept an incoming connection; return True when a client connects."""
|
|
66
105
|
try:
|
|
@@ -76,6 +115,7 @@ class ThreadedServer(threading.Thread, jsocket_base.JsonServer, metaclass=abc.AB
|
|
|
76
115
|
logger.debug("server stopping; accept loop exiting (%s:%s)", self.address, self.port)
|
|
77
116
|
self._is_alive = False
|
|
78
117
|
return False
|
|
118
|
+
self._record_client_start()
|
|
79
119
|
return True
|
|
80
120
|
|
|
81
121
|
def _handle_client_messages(self):
|
|
@@ -99,6 +139,7 @@ class ThreadedServer(threading.Thread, jsocket_base.JsonServer, metaclass=abc.AB
|
|
|
99
139
|
logger.debug("handler error (%s): %s", type(e).__name__, e)
|
|
100
140
|
self._close_connection()
|
|
101
141
|
break
|
|
142
|
+
self._clear_client_stats()
|
|
102
143
|
|
|
103
144
|
def run(self):
|
|
104
145
|
# Ensure the run loop is active even when run() is invoked directly
|
|
@@ -132,6 +173,7 @@ class ThreadedServer(threading.Thread, jsocket_base.JsonServer, metaclass=abc.AB
|
|
|
132
173
|
@retval None
|
|
133
174
|
"""
|
|
134
175
|
self._is_alive = False
|
|
176
|
+
self._clear_client_stats()
|
|
135
177
|
logger.debug("Threaded Server stopped on %s:%s", self.address, self.port)
|
|
136
178
|
|
|
137
179
|
|
|
@@ -144,6 +186,8 @@ class ServerFactoryThread(threading.Thread, jsocket_base.JsonSocket, metaclass=a
|
|
|
144
186
|
self.conn = None
|
|
145
187
|
jsocket_base.JsonSocket.__init__(self, **kwargs)
|
|
146
188
|
self._is_alive = False
|
|
189
|
+
self._client_started_at = None
|
|
190
|
+
self._client_id = None
|
|
147
191
|
|
|
148
192
|
def swap_socket(self, new_sock):
|
|
149
193
|
""" Swaps the existing socket with a new one. Useful for setting socket after a new connection.
|
|
@@ -153,6 +197,12 @@ class ServerFactoryThread(threading.Thread, jsocket_base.JsonSocket, metaclass=a
|
|
|
153
197
|
"""
|
|
154
198
|
self.socket = new_sock
|
|
155
199
|
self.conn = self.socket
|
|
200
|
+
try:
|
|
201
|
+
addr = new_sock.getpeername()
|
|
202
|
+
except OSError:
|
|
203
|
+
addr = None
|
|
204
|
+
self._client_id = _format_client_id(addr)
|
|
205
|
+
self._client_started_at = time.monotonic()
|
|
156
206
|
|
|
157
207
|
def run(self):
|
|
158
208
|
""" Should exit when client closes socket conn.
|
|
@@ -215,6 +265,7 @@ class ServerFactory(ThreadedServer):
|
|
|
215
265
|
raise TypeError("serverThread not of type", ServerFactoryThread)
|
|
216
266
|
self._thread_type = server_thread
|
|
217
267
|
self._threads = []
|
|
268
|
+
self._threads_lock = threading.Lock()
|
|
218
269
|
self._thread_args = kwargs
|
|
219
270
|
self._thread_args.pop('address', None)
|
|
220
271
|
self._thread_args.pop('port', None)
|
|
@@ -245,9 +296,21 @@ class ServerFactory(ThreadedServer):
|
|
|
245
296
|
accepted_conn = self.conn
|
|
246
297
|
# Reset server connection reference so we can accept again
|
|
247
298
|
self._reset_connection_ref()
|
|
299
|
+
if not self._is_alive:
|
|
300
|
+
# Server is stopping; close the accepted connection without spawning a worker.
|
|
301
|
+
try:
|
|
302
|
+
accepted_conn.shutdown(socket.SHUT_RDWR)
|
|
303
|
+
except OSError:
|
|
304
|
+
pass
|
|
305
|
+
try:
|
|
306
|
+
accepted_conn.close()
|
|
307
|
+
except OSError:
|
|
308
|
+
pass
|
|
309
|
+
break
|
|
248
310
|
tmp.swap_socket(accepted_conn)
|
|
249
311
|
tmp.start()
|
|
250
|
-
self.
|
|
312
|
+
with self._threads_lock:
|
|
313
|
+
self._threads.append(tmp)
|
|
251
314
|
break
|
|
252
315
|
|
|
253
316
|
self._wait_to_exit()
|
|
@@ -255,14 +318,20 @@ class ServerFactory(ThreadedServer):
|
|
|
255
318
|
|
|
256
319
|
def stop_all(self):
|
|
257
320
|
"""Stop and join all active worker threads."""
|
|
258
|
-
|
|
259
|
-
|
|
321
|
+
while True:
|
|
322
|
+
with self._threads_lock:
|
|
323
|
+
threads = [t for t in self._threads if t.is_alive()]
|
|
324
|
+
if not threads:
|
|
325
|
+
break
|
|
326
|
+
for t in threads:
|
|
260
327
|
t.force_stop()
|
|
261
328
|
t.join()
|
|
329
|
+
self._purge_threads()
|
|
262
330
|
|
|
263
331
|
def _purge_threads(self):
|
|
264
332
|
# Rebuild list to avoid mutating while iterating
|
|
265
|
-
|
|
333
|
+
with self._threads_lock:
|
|
334
|
+
self._threads = [t for t in self._threads if t.is_alive()]
|
|
266
335
|
|
|
267
336
|
def stop(self):
|
|
268
337
|
# Stop accepting and stop all workers
|
|
@@ -279,6 +348,25 @@ class ServerFactory(ThreadedServer):
|
|
|
279
348
|
time.sleep(0.2)
|
|
280
349
|
|
|
281
350
|
def _get_num_of_active_threads(self):
|
|
282
|
-
|
|
351
|
+
with self._threads_lock:
|
|
352
|
+
threads = list(self._threads)
|
|
353
|
+
return len([True for x in threads if x.is_alive()])
|
|
354
|
+
|
|
355
|
+
def get_client_stats(self) -> dict:
|
|
356
|
+
"""Return connected client count and per-client durations in seconds."""
|
|
357
|
+
with self._threads_lock:
|
|
358
|
+
threads = list(self._threads)
|
|
359
|
+
now = time.monotonic()
|
|
360
|
+
clients = {}
|
|
361
|
+
active = 0
|
|
362
|
+
for t in threads:
|
|
363
|
+
if not t.is_alive():
|
|
364
|
+
continue
|
|
365
|
+
active += 1
|
|
366
|
+
started_at = getattr(t, "_client_started_at", None)
|
|
367
|
+
client_id = getattr(t, "_client_id", None) or f"thread-{t.name}"
|
|
368
|
+
duration = now - started_at if started_at else 0.0
|
|
369
|
+
clients[client_id] = duration
|
|
370
|
+
return {"connected_clients": active, "clients": clients}
|
|
283
371
|
|
|
284
372
|
active = property(_get_num_of_active_threads, doc="number of active threads")
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jsocket
|
|
3
|
+
Version: 1.9.6
|
|
4
|
+
Summary: Python JSON Server & Client
|
|
5
|
+
Author-email: Christopher Piekarski <chris@cpiekarski.com>
|
|
6
|
+
Maintainer-email: Christopher Piekarski <chris@cpiekarski.com>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Project-URL: Homepage, https://cpiekarski.com/2012/01/25/python-json-client-server-redux/
|
|
9
|
+
Keywords: json,socket,server,client
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Topic :: System :: Networking
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
16
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
17
|
+
Classifier: Topic :: System :: Hardware :: Symmetric Multi-processing
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
python-json-socket (jsocket)
|
|
24
|
+
============================
|
|
25
|
+
|
|
26
|
+
[](https://github.com/chris-piekarski/python-json-socket/actions/workflows/ci.yml)
|
|
27
|
+

|
|
28
|
+

|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
Simple JSON-over-TCP sockets for Python. This library provides:
|
|
32
|
+
|
|
33
|
+
- JsonClient/JsonServer: length‑prefixed JSON message framing over TCP
|
|
34
|
+
- ThreadedServer: a single-connection server running in its own thread
|
|
35
|
+
- ServerFactory/ServerFactoryThread: a per‑connection worker model for multiple clients
|
|
36
|
+
|
|
37
|
+
It aims to be small, predictable, and easy to integrate in tests or small services.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
Install
|
|
41
|
+
-------
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pip install jsocket
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Requires Python 3.8+.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
Quickstart
|
|
51
|
+
----------
|
|
52
|
+
|
|
53
|
+
Echo server with `ThreadedServer` and a client:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import time
|
|
57
|
+
import jsocket
|
|
58
|
+
|
|
59
|
+
class Echo(jsocket.ThreadedServer):
|
|
60
|
+
def __init__(self, **kwargs):
|
|
61
|
+
super().__init__(**kwargs)
|
|
62
|
+
self.timeout = 2.0
|
|
63
|
+
|
|
64
|
+
# Return a dict to send a response back to the client
|
|
65
|
+
def _process_message(self, obj):
|
|
66
|
+
if isinstance(obj, dict) and 'echo' in obj:
|
|
67
|
+
return obj
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
# Bind to an ephemeral port (port=0)
|
|
71
|
+
server = Echo(address='127.0.0.1', port=0)
|
|
72
|
+
_, port = server.socket.getsockname()
|
|
73
|
+
server.start()
|
|
74
|
+
|
|
75
|
+
client = jsocket.JsonClient(address='127.0.0.1', port=port)
|
|
76
|
+
assert client.connect() is True
|
|
77
|
+
|
|
78
|
+
payload = {"echo": "hello"}
|
|
79
|
+
client.send_obj(payload)
|
|
80
|
+
assert client.read_obj() == payload
|
|
81
|
+
|
|
82
|
+
client.close()
|
|
83
|
+
server.stop()
|
|
84
|
+
server.join()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Per‑connection workers with `ServerFactory`:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import jsocket
|
|
91
|
+
|
|
92
|
+
class Worker(jsocket.ServerFactoryThread):
|
|
93
|
+
def __init__(self):
|
|
94
|
+
super().__init__()
|
|
95
|
+
self.timeout = 2.0
|
|
96
|
+
|
|
97
|
+
def _process_message(self, obj):
|
|
98
|
+
if isinstance(obj, dict) and 'message' in obj:
|
|
99
|
+
return {"reply": f"got: {obj['message']}"}
|
|
100
|
+
|
|
101
|
+
server = jsocket.ServerFactory(Worker, address='127.0.0.1', port=5489)
|
|
102
|
+
server.start()
|
|
103
|
+
# Connect one or more clients; one Worker is spawned per connection
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
API Highlights
|
|
108
|
+
--------------
|
|
109
|
+
|
|
110
|
+
- JsonClient:
|
|
111
|
+
- `connect()` returns True on success
|
|
112
|
+
- `send_obj(dict)` sends a JSON object
|
|
113
|
+
- `read_obj()` blocks until a full message is received; raises `socket.timeout` or `RuntimeError("socket connection broken")`
|
|
114
|
+
- `timeout` property controls socket timeouts
|
|
115
|
+
|
|
116
|
+
- ThreadedServer:
|
|
117
|
+
- Subclass and implement `_process_message(self, obj) -> Optional[dict]`
|
|
118
|
+
- Return a dict to send a response; return `None` to send nothing
|
|
119
|
+
- `start()`, `stop()`, `join()` manage the server thread
|
|
120
|
+
- `send_obj(dict)` sends to the currently connected client
|
|
121
|
+
|
|
122
|
+
- ServerFactory / ServerFactoryThread:
|
|
123
|
+
- `ServerFactoryThread` is a worker that handles one client connection
|
|
124
|
+
- `ServerFactory` accepts connections and spawns a worker per client
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
Examples and Tests
|
|
128
|
+
------------------
|
|
129
|
+
|
|
130
|
+
- Examples: see `examples/example_servers.py` and `scripts/smoke_test.py`
|
|
131
|
+
- Pytest: end-to-end and listener tests under `tests/`
|
|
132
|
+
- Run: `pytest -q`
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
Behavior-Driven Tests (Behave)
|
|
136
|
+
------------------------------
|
|
137
|
+
|
|
138
|
+
- Steps live under `features/steps/` and environment hooks in `features/environment.py`.
|
|
139
|
+
- To run Behave scenarios, add one or more `.feature` files under `features/` and run:
|
|
140
|
+
- `pip install -r requirements-dev.txt`
|
|
141
|
+
- `PYTHONPATH=. behave -f progress2`
|
|
142
|
+
- A minimal example feature:
|
|
143
|
+
|
|
144
|
+
```gherkin
|
|
145
|
+
Feature: Echo round-trip
|
|
146
|
+
Scenario: client/server echo
|
|
147
|
+
Given I start the server
|
|
148
|
+
And I connect the client
|
|
149
|
+
When the client sends the object {"echo": "hi"}
|
|
150
|
+
Then the client sees a message {"echo": "hi"}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
Notes
|
|
155
|
+
-----
|
|
156
|
+
|
|
157
|
+
- Message framing uses a 4‑byte big‑endian length header followed by a JSON payload encoded as UTF‑8.
|
|
158
|
+
- On disconnect, reads raise `RuntimeError("socket connection broken")` so callers can distinguish cleanly from timeouts.
|
|
159
|
+
- Binding with `port=0` lets the OS choose an ephemeral port; find it with `server.socket.getsockname()`.
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
Links
|
|
163
|
+
-----
|
|
164
|
+
|
|
165
|
+
- PyPI: https://pypi.org/project/jsocket/
|
|
166
|
+
- License: see `LICENSE`
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
jsocket/__init__.py,sha256=V2M4mp2IwcL1Zy_yoD-6Y5h7PO2sdt5Plf1U0Xw27N8,2870
|
|
2
|
+
jsocket/_version.py,sha256=qOzpkpbIx06kwOoN9b1utOqUXOBX3mBIrcqYtuZOMkQ,46
|
|
3
|
+
jsocket/jsocket_base.py,sha256=lc72kDs-6ID-e4006jiVCZPRhVsRIDuQrSV5-oSdah0,8112
|
|
4
|
+
jsocket/tserver.py,sha256=h19so2UgUc1RIMmkNcJamwZ7gbFFGmaSJqrI2l6XUts,14098
|
|
5
|
+
jsocket-1.9.6.dist-info/licenses/LICENSE,sha256=TIwob4kUNx1DKZ0NVKToEDAFgWsevvTgtZgr_obkDhg,11355
|
|
6
|
+
jsocket-1.9.6.dist-info/METADATA,sha256=ebDsoiSN1UF_vqugNBMPWlA54HVCzeodYN7ow1kvwPE,5082
|
|
7
|
+
jsocket-1.9.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
jsocket-1.9.6.dist-info/top_level.txt,sha256=QqfmeUi7avy9cdcsVVvG68CP-4mfg_P6E7OuBuNEcN4,8
|
|
9
|
+
jsocket-1.9.6.dist-info/RECORD,,
|
jsocket-1.9.5.dist-info/METADATA
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: jsocket
|
|
3
|
-
Version: 1.9.5
|
|
4
|
-
Summary: Python JSON Server & Client
|
|
5
|
-
Home-page: https://cpiekarski.com/2012/01/25/python-json-client-server-redux/
|
|
6
|
-
Author: Christopher Piekarski
|
|
7
|
-
Author-email: chris@cpiekarski.com
|
|
8
|
-
Maintainer: Christopher Piekarski
|
|
9
|
-
Maintainer-email: chris@cpiekarski.com
|
|
10
|
-
License: OSI Approved Apache Software License
|
|
11
|
-
Keywords: json,socket,server,client
|
|
12
|
-
Platform: UNKNOWN
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
-
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
18
|
-
Classifier: Topic :: System :: Networking
|
|
19
|
-
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
20
|
-
Classifier: Topic :: System :: Distributed Computing
|
|
21
|
-
Classifier: Topic :: System :: Hardware :: Symmetric Multi-processing
|
|
22
|
-
Provides: jsocket
|
|
23
|
-
Requires-Python: >=3.8
|
|
24
|
-
License-File: LICENSE
|
|
25
|
-
|
|
26
|
-
UNKNOWN
|
|
27
|
-
|
jsocket-1.9.5.dist-info/RECORD
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
jsocket/__init__.py,sha256=Im4nFil0iBOXF0G5K5I2nVMkSDIlGNev-rOjy3XUwn8,2836
|
|
2
|
-
jsocket/jsocket_base.py,sha256=65-0DMdgQKymiE3poBpvRnyTMgEv3-VOfqWe-C-6ge4,8025
|
|
3
|
-
jsocket/tserver.py,sha256=bez8pKWwzPQXD0R33sTV-6Z579DS4vfQnnPkklzxt64,10746
|
|
4
|
-
jsocket-1.9.5.dist-info/LICENSE,sha256=TIwob4kUNx1DKZ0NVKToEDAFgWsevvTgtZgr_obkDhg,11355
|
|
5
|
-
jsocket-1.9.5.dist-info/METADATA,sha256=hV3r6UToy38ZkkrSi8ohRGndzCN_xYa8329Ue_hUhXQ,983
|
|
6
|
-
jsocket-1.9.5.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
|
7
|
-
jsocket-1.9.5.dist-info/top_level.txt,sha256=QqfmeUi7avy9cdcsVVvG68CP-4mfg_P6E7OuBuNEcN4,8
|
|
8
|
-
jsocket-1.9.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|