httpstate 0.0.1__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.
@@ -0,0 +1 @@
1
+ venv
@@ -0,0 +1 @@
1
+ ../LICENSE
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: httpstate
3
+ Version: 0.0.1
4
+ Summary: HTTP State, httpstate.com
5
+ Author-email: "Alex Morales, HTTP State" <alex@httpstate.com>
6
+ License-Expression: AGPL-3.0
7
+ License-File: LICENSE
8
+ Keywords: httpstate
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.9
12
+ Requires-Dist: websockets>=16.0
13
+ Description-Content-Type: text/markdown
14
+
15
+ httpstate.com
@@ -0,0 +1 @@
1
+ httpstate.com
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ build-backend = "hatchling.build"
3
+ requires = ["hatchling >= 1.26"]
4
+
5
+ [project]
6
+ authors = [{ name="Alex Morales, HTTP State", email="alex@httpstate.com" }]
7
+ classifiers = [
8
+ "Operating System :: OS Independent",
9
+ "Programming Language :: Python :: 3"
10
+ ]
11
+ dependencies = ["websockets>=16.0"]
12
+ description = "HTTP State, httpstate.com"
13
+ keywords = ["httpstate"]
14
+ license = "AGPL-3.0"
15
+ license-files = ["LICEN[CS]E*"]
16
+ name = "httpstate"
17
+ readme = "README.md"
18
+ requires-python = ">=3.9"
19
+ version = "0.0.1"
20
+
21
+ [project.url]
22
+ Homepage = "https://httpstate.com"
File without changes
@@ -0,0 +1,112 @@
1
+ # HTTP State, https://httpstate.com/
2
+ # Copyright (C) Alex Morales, 2026
3
+
4
+ # Unless otherwise stated in particular files or directories, this software is free software.
5
+ # You can redistribute it and/or modify it under the terms of the GNU Affero
6
+ # General Public License as published by the Free Software Foundation, either
7
+ # version 3 of the License, or (at your option) any later version.
8
+
9
+ import asyncio
10
+ import threading
11
+ import urllib.error
12
+ import urllib.request
13
+ import websockets
14
+
15
+ from typing import Callable, Dict, List
16
+
17
+ def get(uuid:str) -> None|str:
18
+ try:
19
+ with urllib.request.urlopen(f'https://httpstate.com/{uuid}') as f:
20
+ if f.status == 200:
21
+ return f.read().decode('utf-8', 'replace')
22
+
23
+ return None
24
+ except urllib.error.HTTPError:
25
+ return None
26
+
27
+ def read(uuid:str) -> None|str:
28
+ return get(uuid)
29
+
30
+ def set(uuid:str, data:str) -> None|int:
31
+ req = urllib.request.Request(
32
+ f'https://httpstate.com/{uuid}',
33
+ data=data.encode('utf-8'),
34
+ headers={ 'Content-Type':'text/plain;charset=UTF-8' },
35
+ method='POST'
36
+ )
37
+
38
+ try:
39
+ with urllib.request.urlopen(req) as f:
40
+ return f.status
41
+ except urllib.error.HTTPError:
42
+ return None
43
+
44
+ def write(uuid:str, data:str) -> None|int:
45
+ return set(uuid, data)
46
+
47
+ # HTTP State
48
+ class HttpState:
49
+ def __init__(self, uuid:str):
50
+ self.data:None|str = None
51
+ self.et:Dict[str, List[Callable[[None|str], None]]] = {}
52
+ self.uuid:str = uuid
53
+ self.ws:None|websockets.WebSocketClientProtocol = None
54
+
55
+ threading.Thread(
56
+ daemon=True,
57
+ target=lambda : asyncio.run(self._ws())
58
+ ).start()
59
+
60
+ async def _ws(self):
61
+ self.ws = await websockets.connect(f"wss://httpstate.com/{self.uuid}")
62
+
63
+ await self.ws.send(f'{{"open":"{self.uuid}"}}')
64
+
65
+ async for data in self.ws:
66
+ data = data if isinstance(data, str) else data.decode()
67
+
68
+ if(
69
+ data
70
+ and len(data) > 32
71
+ and data[:32] == self.uuid
72
+ and data[45] == '1'
73
+ ):
74
+ self.emit('change', data[46:])
75
+
76
+ def emit(self, type:str, data:None|str) -> None:
77
+ for callback in self.et.get(type, []):
78
+ callback(data)
79
+
80
+ return self
81
+
82
+ def get(self) -> None|str:
83
+ return get(self.uuid)
84
+
85
+ def off(self, type:str, callback:Callable[[None|str], None]):
86
+ if type in self.et:
87
+ try:
88
+ self.et[type].remove(callback)
89
+ except ValueError:
90
+ pass
91
+
92
+ if not self.et[type]:
93
+ del self.et[type]
94
+
95
+ return self
96
+
97
+ def on(self, type:str, callback:Callable[[None|str], None]) -> None:
98
+ if type not in self.et:
99
+ self.et[type] = []
100
+
101
+ self.et[type].append(callback)
102
+
103
+ return self
104
+
105
+ def read(self) -> None|str:
106
+ return read(self.uuid)
107
+
108
+ def set(self, data:str) -> None|int:
109
+ return set(self.uuid, data)
110
+
111
+ def write(self, data:str) -> None|int:
112
+ return write(self.uuid, data)