httpstate 0.0.13__tar.gz → 0.0.15__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.
- {httpstate-0.0.13 → httpstate-0.0.15}/PKG-INFO +43 -22
- {httpstate-0.0.13 → httpstate-0.0.15}/README.md +40 -19
- {httpstate-0.0.13 → httpstate-0.0.15}/pyproject.toml +3 -3
- {httpstate-0.0.13 → httpstate-0.0.15}/src/httpstate/__init__.py +7 -1
- {httpstate-0.0.13 → httpstate-0.0.15}/src/httpstate/httpstate.py +67 -27
- {httpstate-0.0.13 → httpstate-0.0.15}/.gitignore +0 -0
- {httpstate-0.0.13 → httpstate-0.0.15}/LICENSE +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpstate
|
|
3
|
-
Version: 0.0.
|
|
4
|
-
Summary:
|
|
5
|
-
Author-email: "Alex Morales,
|
|
3
|
+
Version: 0.0.15
|
|
4
|
+
Summary: HTTPState, httpstate.com
|
|
5
|
+
Author-email: "Alex Morales, HTTPState" <alex@httpstate.com>
|
|
6
6
|
License-Expression: AGPL-3.0
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Keywords: httpstate
|
|
@@ -28,7 +28,7 @@ Import
|
|
|
28
28
|
import httpstate
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Pick any valid UUID v4. You can [generate one here](https://
|
|
31
|
+
Pick any valid UUID v4. You can [generate one here](https://uuid.httpstate.com).
|
|
32
32
|
|
|
33
33
|
We'll use `45fb3654-0e92-44da-aa21-ca409c6bdab3` or `45fb36540e9244daaa21ca409c6bdab3` (without dashes).
|
|
34
34
|
|
|
@@ -44,7 +44,7 @@ and retrieve it with
|
|
|
44
44
|
data = httpstate.get('45fb36540e9244daaa21ca409c6bdab3')
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
You can also get
|
|
47
|
+
You can also get realtime updates
|
|
48
48
|
|
|
49
49
|
```python
|
|
50
50
|
hs = httpstate.HttpState('45fb36540e9244daaa21ca409c6bdab3')
|
|
@@ -58,40 +58,61 @@ That's it! 🐙
|
|
|
58
58
|
|
|
59
59
|
### Functions
|
|
60
60
|
|
|
61
|
-
- `get(uuid)`
|
|
62
|
-
Get state of UUIDv4.
|
|
61
|
+
- `get(uuid)`
|
|
62
|
+
Get state of UUIDv4. Returns `None|str`.
|
|
63
63
|
|
|
64
|
-
- `
|
|
64
|
+
- `message.unpack(bytes)`
|
|
65
|
+
Unpack binary WebSocket message into `MessageType(uuid, timestamp, type, value)`.
|
|
66
|
+
|
|
67
|
+
- `post(uuid, data=None)`
|
|
68
|
+
Alias for `set`. Returns `None|int`.
|
|
69
|
+
|
|
70
|
+
- `put(uuid, data=None)`
|
|
71
|
+
Alias for `set`. Returns `None|int`.
|
|
72
|
+
|
|
73
|
+
- `read(uuid)`
|
|
65
74
|
Alias for `get`.
|
|
66
75
|
|
|
67
|
-
- `set(uuid, data)`
|
|
68
|
-
Set state of UUIDv4.
|
|
76
|
+
- `set(uuid, data=None)`
|
|
77
|
+
Set state of UUIDv4. Returns `None|int`. If `data` is `None`, defaults to `''`.
|
|
69
78
|
|
|
70
|
-
- `write(uuid, data)`
|
|
79
|
+
- `write(uuid, data=None)`
|
|
71
80
|
Alias for `set`.
|
|
72
81
|
|
|
73
82
|
### HttpState Class
|
|
74
83
|
|
|
75
|
-
- `HttpState(uuid)`
|
|
84
|
+
- `HttpState(uuid)`
|
|
76
85
|
Create a reactive state instance of UUIDv4.
|
|
86
|
+
- `<HttpState>.data`
|
|
87
|
+
Property with the most up-to-date state value.
|
|
77
88
|
|
|
78
89
|
<br>
|
|
79
90
|
|
|
80
|
-
- `<HttpState>.get()`
|
|
81
|
-
Get state.
|
|
82
|
-
- `<HttpState>.
|
|
91
|
+
- `<HttpState>.get()`
|
|
92
|
+
Get state. Returns `None|str`.
|
|
93
|
+
- `<HttpState>.post(data=None)`
|
|
94
|
+
Alias for `set`.
|
|
95
|
+
- `<HttpState>.put(data=None)`
|
|
96
|
+
Alias for `set`.
|
|
97
|
+
- `<HttpState>.read()`
|
|
83
98
|
Alias for `get`.
|
|
84
|
-
- `<HttpState>.set(data)`
|
|
85
|
-
Set state.
|
|
86
|
-
- `<HttpState>.write(data)`
|
|
99
|
+
- `<HttpState>.set(data=None)`
|
|
100
|
+
Set state. Returns `None|int`. If `data` is `None`, defaults to `''`.
|
|
101
|
+
- `<HttpState>.write(data=None)`
|
|
87
102
|
Alias for `set`.
|
|
88
103
|
|
|
89
104
|
<br>
|
|
90
105
|
|
|
91
|
-
- `<HttpState>.off(type, callback)`
|
|
92
|
-
Unsubscribe from
|
|
93
|
-
- `<HttpState>.on(type, callback)`
|
|
94
|
-
Subscribe to
|
|
106
|
+
- `<HttpState>.off(type, callback)`
|
|
107
|
+
Unsubscribe from realtime updates.
|
|
108
|
+
- `<HttpState>.on(type, callback)`
|
|
109
|
+
Subscribe to realtime updates.
|
|
110
|
+
- `change`: fired when state data changes. Callback receives current data as argument.
|
|
111
|
+
|
|
112
|
+
<br>
|
|
113
|
+
|
|
114
|
+
- `<HttpState>.delete()`
|
|
115
|
+
Cleanup and delete the instance.
|
|
95
116
|
|
|
96
117
|
---
|
|
97
118
|
|
|
@@ -14,7 +14,7 @@ Import
|
|
|
14
14
|
import httpstate
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Pick any valid UUID v4. You can [generate one here](https://
|
|
17
|
+
Pick any valid UUID v4. You can [generate one here](https://uuid.httpstate.com).
|
|
18
18
|
|
|
19
19
|
We'll use `45fb3654-0e92-44da-aa21-ca409c6bdab3` or `45fb36540e9244daaa21ca409c6bdab3` (without dashes).
|
|
20
20
|
|
|
@@ -30,7 +30,7 @@ and retrieve it with
|
|
|
30
30
|
data = httpstate.get('45fb36540e9244daaa21ca409c6bdab3')
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
You can also get
|
|
33
|
+
You can also get realtime updates
|
|
34
34
|
|
|
35
35
|
```python
|
|
36
36
|
hs = httpstate.HttpState('45fb36540e9244daaa21ca409c6bdab3')
|
|
@@ -44,40 +44,61 @@ That's it! 🐙
|
|
|
44
44
|
|
|
45
45
|
### Functions
|
|
46
46
|
|
|
47
|
-
- `get(uuid)`
|
|
48
|
-
Get state of UUIDv4.
|
|
47
|
+
- `get(uuid)`
|
|
48
|
+
Get state of UUIDv4. Returns `None|str`.
|
|
49
49
|
|
|
50
|
-
- `
|
|
50
|
+
- `message.unpack(bytes)`
|
|
51
|
+
Unpack binary WebSocket message into `MessageType(uuid, timestamp, type, value)`.
|
|
52
|
+
|
|
53
|
+
- `post(uuid, data=None)`
|
|
54
|
+
Alias for `set`. Returns `None|int`.
|
|
55
|
+
|
|
56
|
+
- `put(uuid, data=None)`
|
|
57
|
+
Alias for `set`. Returns `None|int`.
|
|
58
|
+
|
|
59
|
+
- `read(uuid)`
|
|
51
60
|
Alias for `get`.
|
|
52
61
|
|
|
53
|
-
- `set(uuid, data)`
|
|
54
|
-
Set state of UUIDv4.
|
|
62
|
+
- `set(uuid, data=None)`
|
|
63
|
+
Set state of UUIDv4. Returns `None|int`. If `data` is `None`, defaults to `''`.
|
|
55
64
|
|
|
56
|
-
- `write(uuid, data)`
|
|
65
|
+
- `write(uuid, data=None)`
|
|
57
66
|
Alias for `set`.
|
|
58
67
|
|
|
59
68
|
### HttpState Class
|
|
60
69
|
|
|
61
|
-
- `HttpState(uuid)`
|
|
70
|
+
- `HttpState(uuid)`
|
|
62
71
|
Create a reactive state instance of UUIDv4.
|
|
72
|
+
- `<HttpState>.data`
|
|
73
|
+
Property with the most up-to-date state value.
|
|
63
74
|
|
|
64
75
|
<br>
|
|
65
76
|
|
|
66
|
-
- `<HttpState>.get()`
|
|
67
|
-
Get state.
|
|
68
|
-
- `<HttpState>.
|
|
77
|
+
- `<HttpState>.get()`
|
|
78
|
+
Get state. Returns `None|str`.
|
|
79
|
+
- `<HttpState>.post(data=None)`
|
|
80
|
+
Alias for `set`.
|
|
81
|
+
- `<HttpState>.put(data=None)`
|
|
82
|
+
Alias for `set`.
|
|
83
|
+
- `<HttpState>.read()`
|
|
69
84
|
Alias for `get`.
|
|
70
|
-
- `<HttpState>.set(data)`
|
|
71
|
-
Set state.
|
|
72
|
-
- `<HttpState>.write(data)`
|
|
85
|
+
- `<HttpState>.set(data=None)`
|
|
86
|
+
Set state. Returns `None|int`. If `data` is `None`, defaults to `''`.
|
|
87
|
+
- `<HttpState>.write(data=None)`
|
|
73
88
|
Alias for `set`.
|
|
74
89
|
|
|
75
90
|
<br>
|
|
76
91
|
|
|
77
|
-
- `<HttpState>.off(type, callback)`
|
|
78
|
-
Unsubscribe from
|
|
79
|
-
- `<HttpState>.on(type, callback)`
|
|
80
|
-
Subscribe to
|
|
92
|
+
- `<HttpState>.off(type, callback)`
|
|
93
|
+
Unsubscribe from realtime updates.
|
|
94
|
+
- `<HttpState>.on(type, callback)`
|
|
95
|
+
Subscribe to realtime updates.
|
|
96
|
+
- `change`: fired when state data changes. Callback receives current data as argument.
|
|
97
|
+
|
|
98
|
+
<br>
|
|
99
|
+
|
|
100
|
+
- `<HttpState>.delete()`
|
|
101
|
+
Cleanup and delete the instance.
|
|
81
102
|
|
|
82
103
|
---
|
|
83
104
|
|
|
@@ -3,20 +3,20 @@ build-backend = "hatchling.build"
|
|
|
3
3
|
requires = ["hatchling >= 1.26"]
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
|
-
authors = [{ email="alex@httpstate.com", name="Alex Morales,
|
|
6
|
+
authors = [{ email="alex@httpstate.com", name="Alex Morales, HTTPState" }]
|
|
7
7
|
classifiers = [
|
|
8
8
|
"Operating System :: OS Independent",
|
|
9
9
|
"Programming Language :: Python :: 3"
|
|
10
10
|
]
|
|
11
11
|
dependencies = ["websockets>=16.0"]
|
|
12
|
-
description = "
|
|
12
|
+
description = "HTTPState, httpstate.com"
|
|
13
13
|
keywords = ["httpstate"]
|
|
14
14
|
license = "AGPL-3.0"
|
|
15
15
|
license-files = ["LICEN[CS]E*"]
|
|
16
16
|
name = "httpstate"
|
|
17
17
|
readme = "README.md"
|
|
18
18
|
requires-python = ">=3.10"
|
|
19
|
-
version = "0.0.
|
|
19
|
+
version = "0.0.15"
|
|
20
20
|
|
|
21
21
|
[project.url]
|
|
22
22
|
Homepage = "https://httpstate.com"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# HTTPState, https://httpstate.com/
|
|
2
2
|
# Copyright (C) Alex Morales, 2026
|
|
3
3
|
|
|
4
4
|
# Unless otherwise stated in particular files or directories, this software is free software.
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
from .httpstate import(
|
|
10
10
|
get,
|
|
11
|
+
message,
|
|
12
|
+
post,
|
|
13
|
+
put,
|
|
11
14
|
read,
|
|
12
15
|
set,
|
|
13
16
|
write,
|
|
@@ -16,6 +19,9 @@ from .httpstate import(
|
|
|
16
19
|
|
|
17
20
|
__all__ = [
|
|
18
21
|
'get',
|
|
22
|
+
'message',
|
|
23
|
+
'post',
|
|
24
|
+
'put',
|
|
19
25
|
'read',
|
|
20
26
|
'set',
|
|
21
27
|
'write',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# HTTPState, https://httpstate.com/
|
|
2
2
|
# Copyright (C) Alex Morales, 2026
|
|
3
3
|
|
|
4
4
|
# Unless otherwise stated in particular files or directories, this software is free software.
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
# version 3 of the License, or (at your option) any later version.
|
|
8
8
|
|
|
9
9
|
import asyncio
|
|
10
|
+
import struct
|
|
10
11
|
import threading
|
|
11
12
|
import urllib.error
|
|
12
13
|
import urllib.request
|
|
@@ -26,11 +27,41 @@ def get(uuid:str) -> None|str:
|
|
|
26
27
|
except Exception:
|
|
27
28
|
return None
|
|
28
29
|
|
|
30
|
+
class MessageType:
|
|
31
|
+
def __init__(self, uuid:str, timestamp:int, type:int, value:bytes) -> None:
|
|
32
|
+
self.uuid:str = uuid
|
|
33
|
+
self.timestamp:int = timestamp
|
|
34
|
+
self.type:int = type
|
|
35
|
+
self.value:bytes = value
|
|
36
|
+
|
|
37
|
+
class Message:
|
|
38
|
+
@staticmethod
|
|
39
|
+
def unpack(b:bytes) -> MessageType:
|
|
40
|
+
length:int = b[0]
|
|
41
|
+
|
|
42
|
+
return MessageType(
|
|
43
|
+
uuid=b[1:1+length].decode('utf-8'),
|
|
44
|
+
timestamp=struct.unpack_from('>Q', b, 1+length)[0],
|
|
45
|
+
type=b[1+length+8],
|
|
46
|
+
value=b[1+length+9:],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
message:type = Message
|
|
50
|
+
|
|
51
|
+
def post(uuid:str, data:None|str = None) -> None|int:
|
|
52
|
+
return set(uuid, data)
|
|
53
|
+
|
|
54
|
+
def put(uuid:str, data:None|str = None) -> None|int:
|
|
55
|
+
return set(uuid, data)
|
|
56
|
+
|
|
29
57
|
def read(uuid:str) -> None|str:
|
|
30
58
|
return get(uuid)
|
|
31
59
|
|
|
32
|
-
def set(uuid:str, data:str) -> None|int:
|
|
33
|
-
|
|
60
|
+
def set(uuid:str, data:None|str = None) -> None|int:
|
|
61
|
+
if(data is None):
|
|
62
|
+
data = ''
|
|
63
|
+
|
|
64
|
+
req:urllib.request.Request = urllib.request.Request(
|
|
34
65
|
f'https://httpstate.com/{uuid}',
|
|
35
66
|
data=data.encode('utf-8'),
|
|
36
67
|
headers={ 'Content-Type':'text/plain;charset=UTF-8' },
|
|
@@ -45,15 +76,16 @@ def set(uuid:str, data:str) -> None|int:
|
|
|
45
76
|
except Exception:
|
|
46
77
|
return None
|
|
47
78
|
|
|
48
|
-
def write(uuid:str, data:str) -> None|int:
|
|
79
|
+
def write(uuid:str, data:None|str = None) -> None|int:
|
|
49
80
|
return set(uuid, data)
|
|
50
81
|
|
|
51
|
-
#
|
|
82
|
+
# HTTPState
|
|
52
83
|
class HttpState:
|
|
53
|
-
def __init__(self, uuid:str):
|
|
84
|
+
def __init__(self, uuid:str) -> None:
|
|
54
85
|
self.data:None|str = None
|
|
55
86
|
self.el:None|asyncio.AbstractEventLoop = None
|
|
56
87
|
self.et:Dict[str, List[Callable[[None|str], None]]] = {}
|
|
88
|
+
self.lock:threading.Lock = threading.Lock()
|
|
57
89
|
self.uuid:str = uuid
|
|
58
90
|
self.ws:None|websockets.WebSocketClientProtocol = None
|
|
59
91
|
|
|
@@ -67,8 +99,8 @@ class HttpState:
|
|
|
67
99
|
target=lambda : asyncio.run(self._ws())
|
|
68
100
|
).start()
|
|
69
101
|
|
|
70
|
-
def _el(self):
|
|
71
|
-
self.el = asyncio.new_event_loop()
|
|
102
|
+
def _el(self) -> None:
|
|
103
|
+
self.el:asyncio.AbstractEventLoop = asyncio.new_event_loop()
|
|
72
104
|
|
|
73
105
|
asyncio.set_event_loop(self.el)
|
|
74
106
|
|
|
@@ -76,29 +108,29 @@ class HttpState:
|
|
|
76
108
|
|
|
77
109
|
self.el.run_forever()
|
|
78
110
|
|
|
79
|
-
async def _ws(self):
|
|
80
|
-
self.ws = await websockets.connect(f"wss://httpstate.com/{self.uuid}")
|
|
111
|
+
async def _ws(self) -> None:
|
|
112
|
+
self.ws:websockets.WebSocketClientProtocol = await websockets.connect(f"wss://httpstate.com/{self.uuid}")
|
|
81
113
|
|
|
82
114
|
await self.ws.send(f'{{"open":"{self.uuid}"}}')
|
|
83
115
|
self.emit('open')
|
|
84
116
|
|
|
85
|
-
async def data():
|
|
117
|
+
async def data() -> None:
|
|
86
118
|
async for data in self.ws:
|
|
87
|
-
data =
|
|
119
|
+
data:MessageType = message.unpack(data)
|
|
88
120
|
|
|
89
121
|
if(
|
|
90
122
|
data
|
|
91
|
-
and
|
|
92
|
-
and data
|
|
93
|
-
and data[45] == '1'
|
|
123
|
+
and data.uuid == self.uuid
|
|
124
|
+
and data.type == 1
|
|
94
125
|
):
|
|
95
|
-
self.
|
|
126
|
+
with self.lock:
|
|
127
|
+
self.data:None|str = data.value.decode()
|
|
96
128
|
|
|
97
129
|
self.emit('change', self.data)
|
|
98
130
|
|
|
99
131
|
asyncio.create_task(data())
|
|
100
132
|
|
|
101
|
-
async def interval():
|
|
133
|
+
async def interval() -> None:
|
|
102
134
|
while True:
|
|
103
135
|
try:
|
|
104
136
|
await self.ws.ping()
|
|
@@ -111,10 +143,10 @@ class HttpState:
|
|
|
111
143
|
|
|
112
144
|
await asyncio.Event().wait()
|
|
113
145
|
|
|
114
|
-
def
|
|
146
|
+
def delete(self) -> None:
|
|
115
147
|
pass
|
|
116
148
|
|
|
117
|
-
def emit(self, type:str, data:None|str = None):
|
|
149
|
+
def emit(self, type:str, data:None|str = None) -> "HttpState":
|
|
118
150
|
for callback in self.et.get(type, []):
|
|
119
151
|
if(data is None):
|
|
120
152
|
callback()
|
|
@@ -124,16 +156,18 @@ class HttpState:
|
|
|
124
156
|
return self
|
|
125
157
|
|
|
126
158
|
def get(self) -> None|str:
|
|
127
|
-
data = get(self.uuid)
|
|
159
|
+
data:None|str = get(self.uuid)
|
|
128
160
|
|
|
129
|
-
|
|
130
|
-
|
|
161
|
+
with self.lock:
|
|
162
|
+
if(data != self.data):
|
|
163
|
+
if self.el is not None:
|
|
164
|
+
self.el.call_soon_threadsafe(lambda : self.emit('change', self.data))
|
|
131
165
|
|
|
132
|
-
|
|
166
|
+
self.data = data
|
|
133
167
|
|
|
134
168
|
return self.data
|
|
135
169
|
|
|
136
|
-
def off(self, type:str, callback:Callable[[None|str], None]):
|
|
170
|
+
def off(self, type:str, callback:Callable[[None|str], None]) -> "HttpState":
|
|
137
171
|
if type in self.et:
|
|
138
172
|
try:
|
|
139
173
|
self.et[type].remove(callback)
|
|
@@ -145,19 +179,25 @@ class HttpState:
|
|
|
145
179
|
|
|
146
180
|
return self
|
|
147
181
|
|
|
148
|
-
def on(self, type:str, callback:Callable[[None|str], None]):
|
|
182
|
+
def on(self, type:str, callback:Callable[[None|str], None]) -> "HttpState":
|
|
149
183
|
if type not in self.et:
|
|
150
184
|
self.et[type] = []
|
|
151
185
|
|
|
152
186
|
self.et[type].append(callback)
|
|
153
187
|
|
|
154
188
|
return self
|
|
189
|
+
|
|
190
|
+
def post(self, data:None|str = None) -> None|int:
|
|
191
|
+
return self.set(data)
|
|
192
|
+
|
|
193
|
+
def put(self, data:None|str = None) -> None|int:
|
|
194
|
+
return self.set(data)
|
|
155
195
|
|
|
156
196
|
def read(self) -> None|str:
|
|
157
197
|
return self.get()
|
|
158
198
|
|
|
159
|
-
def set(self, data:str) -> None|int:
|
|
199
|
+
def set(self, data:None|str = None) -> None|int:
|
|
160
200
|
return set(self.uuid, data)
|
|
161
201
|
|
|
162
|
-
def write(self, data:str) -> None|int:
|
|
202
|
+
def write(self, data:None|str = None) -> None|int:
|
|
163
203
|
return self.set(data)
|
|
File without changes
|
|
File without changes
|