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.
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: httpstate
3
- Version: 0.0.13
4
- Summary: HTTP State, httpstate.com
5
- Author-email: "Alex Morales, HTTP State" <alex@httpstate.com>
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://www.uuidgenerator.net/version4).
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 real-time updates
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
- - `read(uuid)`
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>.read()`
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 real-time updates.
93
- - `<HttpState>.on(type, callback)`
94
- Subscribe to real-time updates (type = `change`).
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://www.uuidgenerator.net/version4).
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 real-time updates
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
- - `read(uuid)`
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>.read()`
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 real-time updates.
79
- - `<HttpState>.on(type, callback)`
80
- Subscribe to real-time updates (type = `change`).
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, HTTP State" }]
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 = "HTTP State, httpstate.com"
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.13"
19
+ version = "0.0.15"
20
20
 
21
21
  [project.url]
22
22
  Homepage = "https://httpstate.com"
@@ -1,4 +1,4 @@
1
- # HTTP State, https://httpstate.com/
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
- # HTTP State, https://httpstate.com/
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
- req = urllib.request.Request(
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
- # HTTP State
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 = data.decode()
119
+ data:MessageType = message.unpack(data)
88
120
 
89
121
  if(
90
122
  data
91
- and len(data) > 32
92
- and data[:32] == self.uuid
93
- and data[45] == '1'
123
+ and data.uuid == self.uuid
124
+ and data.type == 1
94
125
  ):
95
- self.data = data[46:]
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 destroy(self):
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
- if(data != self.data):
130
- self.el.call_soon_threadsafe(lambda : self.emit('change', self.data))
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
- self.data = data
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