roughly 0.1.0__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.
- roughly-0.1.0/LICENSE +21 -0
- roughly-0.1.0/PKG-INFO +228 -0
- roughly-0.1.0/README.md +199 -0
- roughly-0.1.0/pyproject.toml +111 -0
- roughly-0.1.0/roughly/__init__.py +1 -0
- roughly-0.1.0/roughly/cli.py +336 -0
- roughly-0.1.0/roughly/client.py +324 -0
- roughly-0.1.0/roughly/ecosystem.py +287 -0
- roughly-0.1.0/roughly/errors.py +38 -0
- roughly-0.1.0/roughly/models.py +416 -0
- roughly-0.1.0/roughly/py.typed +0 -0
- roughly-0.1.0/roughly/server.py +634 -0
- roughly-0.1.0/roughly/shared.py +211 -0
- roughly-0.1.0/roughly/tags.py +25 -0
roughly-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 teaishealthy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
roughly-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: roughly
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An asynchronous Roughtime implementation for Python
|
|
5
|
+
Keywords: roughtime,time,ntp,synchronization,asyncio
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: Topic :: Internet
|
|
16
|
+
Classifier: Topic :: Security :: Cryptography
|
|
17
|
+
Classifier: Topic :: System :: Networking
|
|
18
|
+
Classifier: Topic :: System :: Networking :: Time Synchronization
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Classifier: Framework :: AsyncIO
|
|
22
|
+
Requires-Dist: cryptography>=46.0.3
|
|
23
|
+
Requires-Dist: click>=8.3.1 ; extra == 'cli'
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Project-URL: Repository, https://github.com/teaishealthy/roughly
|
|
26
|
+
Project-URL: Issues, https://github.com/teaishealthy/roughly/issues
|
|
27
|
+
Provides-Extra: cli
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# roughly
|
|
31
|
+
|
|
32
|
+
[](https://github.com/astral-sh/ruff)
|
|
33
|
+

|
|
34
|
+

|
|
35
|
+
[](https://datatracker.ietf.org/doc/html/draft-ietf-ntp-roughtime-19)
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
An asynchronous implemenation of the Roughtime protocol for Python.
|
|
39
|
+
|
|
40
|
+
Implements the Roughtime protocol as described in https://datatracker.ietf.org/doc/html/draft-ietf-ntp-roughtime-19.
|
|
41
|
+
|
|
42
|
+
Draft versions 07 through 19 are supported for querying servers.\
|
|
43
|
+
Draft versions 10 through 19 are supported for running a server. Also supports queries from Google Roughtime clients.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
### Installation
|
|
49
|
+
You can install `roughly` from PyPI using your favorite package manager, for example with `pip`:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install roughly
|
|
53
|
+
# or with the cli extra
|
|
54
|
+
pip install roughly[cli]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### As a CLI
|
|
58
|
+
|
|
59
|
+
#### Querying
|
|
60
|
+
|
|
61
|
+
You can use `roughly` as a command line tool to query Roughtime servers.
|
|
62
|
+
Install `roughly` with the `cli` extra using your favorite CLI package manager, for example with `uv` (or `pipx`):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv tool install roughly[cli]
|
|
66
|
+
pipx install roughly[cli]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then you can query a Roughtime server like so:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
roughly query time.teax.dev 2002 84pMADvKUcSOq5RNbVRjVrjiU16Dxo2XV2Qkm+4DRTg=
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or run ecosystem queries (assuming you have an `ecosystem.json` file):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
roughly ecosystem malfeasance
|
|
79
|
+
roughly ecosystem state
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### Running a server
|
|
83
|
+
|
|
84
|
+
You can also run your own Roughtime server using `roughly`.
|
|
85
|
+
|
|
86
|
+
First, generate a keypair:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
roughly server keygen
|
|
90
|
+
```
|
|
91
|
+
This will output a .env file containing the server's private key.
|
|
92
|
+
|
|
93
|
+
You can then run the server like so:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
ROUGHLY_SERVER_PRIVATE_KEY="your_private_key_here" roughly -v server run
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
By default, the server will bind to `0.0.0.0:2002`. You can change this using the `--host` and `--port` flags.
|
|
100
|
+
I recommend running the server with verbose logging enabled (`-v`), so you can see incoming requests and debug any issues.
|
|
101
|
+
Additionally you might want to consider turning off response greasing while testing using the `--no-grease` flag.
|
|
102
|
+
|
|
103
|
+
### As a library
|
|
104
|
+
|
|
105
|
+
#### Querying
|
|
106
|
+
|
|
107
|
+
`roughly` can be used as an asynchronous library to query Roughtime servers from your own Python code.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import roughly.client
|
|
111
|
+
|
|
112
|
+
response = await roughly.client.send_request(
|
|
113
|
+
host="time.teax.dev",
|
|
114
|
+
port=2002,
|
|
115
|
+
public_key=base64.b64decode(b"84pMADvKUcSOq5RNbVRjVrjiU16Dxo2XV2Qkm+4DRTg=")
|
|
116
|
+
)
|
|
117
|
+
# Responses are always verified before being returned
|
|
118
|
+
|
|
119
|
+
print("Current time:", response.signed_response.midpoint)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
You can also use the built-in ecosystem tools to query multiple servers and check for malfeasance as described in the RFC.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from pathlib import Path
|
|
126
|
+
import json
|
|
127
|
+
|
|
128
|
+
from roughly.ecosystem import (
|
|
129
|
+
confirm_malfeasance,
|
|
130
|
+
load_ecosystem,
|
|
131
|
+
malfeasance_report,
|
|
132
|
+
pick_servers,
|
|
133
|
+
query_servers,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
ecosystem = load_ecosystem(Path("ecosystem.json"))
|
|
137
|
+
selected_servers = await pick_servers(ecosystem)
|
|
138
|
+
responses = await query_servers(selected_servers)
|
|
139
|
+
report = malfeasance_report(responses, selected_servers)
|
|
140
|
+
|
|
141
|
+
if confirm_malfeasance(report):
|
|
142
|
+
print("something scary is going on!")
|
|
143
|
+
with open("malfeasance_report.json", "w") as f:
|
|
144
|
+
json.dump(report, f, indent=2)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Running a server
|
|
148
|
+
|
|
149
|
+
You can also programmatically run your own Roughtime server:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
import roughly.server
|
|
153
|
+
|
|
154
|
+
server = roughly.server.Server.create() # generates a new keypair
|
|
155
|
+
await roughly.server.serve(server)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Why? You can subclass `roughly.server.UDPHandler` and `roughly.server.Server` to implement custom behavior. Like a malfeasant server for testing:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
import roughly
|
|
162
|
+
import roughly.server
|
|
163
|
+
|
|
164
|
+
class ScaryServer(roughly.server.Server):
|
|
165
|
+
@staticmethod
|
|
166
|
+
def get_time() -> int:
|
|
167
|
+
# return a wrong-ish time
|
|
168
|
+
return int(time.time()) + random.randint(-3600, 3600)
|
|
169
|
+
|
|
170
|
+
await roughly.server.serve(ScaryServer.create())
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Ecosystem
|
|
174
|
+
|
|
175
|
+
An example ecosystem file can be found at [ecosystem.json](ecosystem.json), I tried my best to include as many servers as I could find.
|
|
176
|
+
|
|
177
|
+
If you know of any other Roughtime servers, run your own server, or have updated public keys for any of the listed servers, please open a PR or an issue!
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
## Interoperability
|
|
181
|
+
|
|
182
|
+
The interopability matrix of `roughly` against Roughtime servers looks like this:
|
|
183
|
+
|
|
184
|
+
### Roughly as a client
|
|
185
|
+
|
|
186
|
+
| Server | Result |
|
|
187
|
+
|---|---:|
|
|
188
|
+
| [butterfield](https://github.com/signalsforgranted/butterfield) | ✅ |
|
|
189
|
+
| [cloudflare](https://github.com/cloudflare/roughtime) | ✅ |
|
|
190
|
+
| [pyroughtime](https://github.com/dansarie/pyroughtime) | ✅ |
|
|
191
|
+
| [roughenough](https://github.com/int08h/roughenough/) | ⚠️ |
|
|
192
|
+
| [roughtimed](https://github.com/dansarie/roughtimed) | ✅ |
|
|
193
|
+
| roughly | ✅ |
|
|
194
|
+
|
|
195
|
+
⚠️ `roughenough` only expects version `0x8000000c` and does not ignore unknown versions.
|
|
196
|
+
Make sure to explicitly request only version `0x8000000c` when querying `roughenough` servers, i.e.:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
await roughly.client.send_request(
|
|
200
|
+
# <snip!>
|
|
201
|
+
versions=(0x8000000c,),
|
|
202
|
+
)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Roughly as a server
|
|
206
|
+
|
|
207
|
+
| Client | Result |
|
|
208
|
+
|---|---:|
|
|
209
|
+
| cloudflare | ✅ |
|
|
210
|
+
| craggy | ✅ |
|
|
211
|
+
| node-roughtime | ✅ |
|
|
212
|
+
| pyroughtime | ✅ |
|
|
213
|
+
| roughenough | ❌ |
|
|
214
|
+
| roughly | ✅ |
|
|
215
|
+
| vroughtime | ✅ |
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
### draft-7
|
|
221
|
+
|
|
222
|
+
Support for draft-7 is limited, in the sense that `roughly` will fit responses from draft-7 servers into the draft-15 data structures.
|
|
223
|
+
This means that some fields that are not present in draft-8+ (such as DUT1, DTAI, and LEAP) will be missing.
|
|
224
|
+
Additionally draft-7 offered for the precision of radius to be in microseconds, while draft-8+ uses seconds, this precision will be lost when querying draft-7 servers, and be clamped to a minimum of one second.
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
roughly-0.1.0/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# roughly
|
|
2
|
+
|
|
3
|
+
[](https://github.com/astral-sh/ruff)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
[](https://datatracker.ietf.org/doc/html/draft-ietf-ntp-roughtime-19)
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
An asynchronous implemenation of the Roughtime protocol for Python.
|
|
10
|
+
|
|
11
|
+
Implements the Roughtime protocol as described in https://datatracker.ietf.org/doc/html/draft-ietf-ntp-roughtime-19.
|
|
12
|
+
|
|
13
|
+
Draft versions 07 through 19 are supported for querying servers.\
|
|
14
|
+
Draft versions 10 through 19 are supported for running a server. Also supports queries from Google Roughtime clients.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
### Installation
|
|
20
|
+
You can install `roughly` from PyPI using your favorite package manager, for example with `pip`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install roughly
|
|
24
|
+
# or with the cli extra
|
|
25
|
+
pip install roughly[cli]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### As a CLI
|
|
29
|
+
|
|
30
|
+
#### Querying
|
|
31
|
+
|
|
32
|
+
You can use `roughly` as a command line tool to query Roughtime servers.
|
|
33
|
+
Install `roughly` with the `cli` extra using your favorite CLI package manager, for example with `uv` (or `pipx`):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv tool install roughly[cli]
|
|
37
|
+
pipx install roughly[cli]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then you can query a Roughtime server like so:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
roughly query time.teax.dev 2002 84pMADvKUcSOq5RNbVRjVrjiU16Dxo2XV2Qkm+4DRTg=
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or run ecosystem queries (assuming you have an `ecosystem.json` file):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
roughly ecosystem malfeasance
|
|
50
|
+
roughly ecosystem state
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### Running a server
|
|
54
|
+
|
|
55
|
+
You can also run your own Roughtime server using `roughly`.
|
|
56
|
+
|
|
57
|
+
First, generate a keypair:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
roughly server keygen
|
|
61
|
+
```
|
|
62
|
+
This will output a .env file containing the server's private key.
|
|
63
|
+
|
|
64
|
+
You can then run the server like so:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
ROUGHLY_SERVER_PRIVATE_KEY="your_private_key_here" roughly -v server run
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
By default, the server will bind to `0.0.0.0:2002`. You can change this using the `--host` and `--port` flags.
|
|
71
|
+
I recommend running the server with verbose logging enabled (`-v`), so you can see incoming requests and debug any issues.
|
|
72
|
+
Additionally you might want to consider turning off response greasing while testing using the `--no-grease` flag.
|
|
73
|
+
|
|
74
|
+
### As a library
|
|
75
|
+
|
|
76
|
+
#### Querying
|
|
77
|
+
|
|
78
|
+
`roughly` can be used as an asynchronous library to query Roughtime servers from your own Python code.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import roughly.client
|
|
82
|
+
|
|
83
|
+
response = await roughly.client.send_request(
|
|
84
|
+
host="time.teax.dev",
|
|
85
|
+
port=2002,
|
|
86
|
+
public_key=base64.b64decode(b"84pMADvKUcSOq5RNbVRjVrjiU16Dxo2XV2Qkm+4DRTg=")
|
|
87
|
+
)
|
|
88
|
+
# Responses are always verified before being returned
|
|
89
|
+
|
|
90
|
+
print("Current time:", response.signed_response.midpoint)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
You can also use the built-in ecosystem tools to query multiple servers and check for malfeasance as described in the RFC.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from pathlib import Path
|
|
97
|
+
import json
|
|
98
|
+
|
|
99
|
+
from roughly.ecosystem import (
|
|
100
|
+
confirm_malfeasance,
|
|
101
|
+
load_ecosystem,
|
|
102
|
+
malfeasance_report,
|
|
103
|
+
pick_servers,
|
|
104
|
+
query_servers,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
ecosystem = load_ecosystem(Path("ecosystem.json"))
|
|
108
|
+
selected_servers = await pick_servers(ecosystem)
|
|
109
|
+
responses = await query_servers(selected_servers)
|
|
110
|
+
report = malfeasance_report(responses, selected_servers)
|
|
111
|
+
|
|
112
|
+
if confirm_malfeasance(report):
|
|
113
|
+
print("something scary is going on!")
|
|
114
|
+
with open("malfeasance_report.json", "w") as f:
|
|
115
|
+
json.dump(report, f, indent=2)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Running a server
|
|
119
|
+
|
|
120
|
+
You can also programmatically run your own Roughtime server:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import roughly.server
|
|
124
|
+
|
|
125
|
+
server = roughly.server.Server.create() # generates a new keypair
|
|
126
|
+
await roughly.server.serve(server)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Why? You can subclass `roughly.server.UDPHandler` and `roughly.server.Server` to implement custom behavior. Like a malfeasant server for testing:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import roughly
|
|
133
|
+
import roughly.server
|
|
134
|
+
|
|
135
|
+
class ScaryServer(roughly.server.Server):
|
|
136
|
+
@staticmethod
|
|
137
|
+
def get_time() -> int:
|
|
138
|
+
# return a wrong-ish time
|
|
139
|
+
return int(time.time()) + random.randint(-3600, 3600)
|
|
140
|
+
|
|
141
|
+
await roughly.server.serve(ScaryServer.create())
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Ecosystem
|
|
145
|
+
|
|
146
|
+
An example ecosystem file can be found at [ecosystem.json](ecosystem.json), I tried my best to include as many servers as I could find.
|
|
147
|
+
|
|
148
|
+
If you know of any other Roughtime servers, run your own server, or have updated public keys for any of the listed servers, please open a PR or an issue!
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
## Interoperability
|
|
152
|
+
|
|
153
|
+
The interopability matrix of `roughly` against Roughtime servers looks like this:
|
|
154
|
+
|
|
155
|
+
### Roughly as a client
|
|
156
|
+
|
|
157
|
+
| Server | Result |
|
|
158
|
+
|---|---:|
|
|
159
|
+
| [butterfield](https://github.com/signalsforgranted/butterfield) | ✅ |
|
|
160
|
+
| [cloudflare](https://github.com/cloudflare/roughtime) | ✅ |
|
|
161
|
+
| [pyroughtime](https://github.com/dansarie/pyroughtime) | ✅ |
|
|
162
|
+
| [roughenough](https://github.com/int08h/roughenough/) | ⚠️ |
|
|
163
|
+
| [roughtimed](https://github.com/dansarie/roughtimed) | ✅ |
|
|
164
|
+
| roughly | ✅ |
|
|
165
|
+
|
|
166
|
+
⚠️ `roughenough` only expects version `0x8000000c` and does not ignore unknown versions.
|
|
167
|
+
Make sure to explicitly request only version `0x8000000c` when querying `roughenough` servers, i.e.:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
await roughly.client.send_request(
|
|
171
|
+
# <snip!>
|
|
172
|
+
versions=(0x8000000c,),
|
|
173
|
+
)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Roughly as a server
|
|
177
|
+
|
|
178
|
+
| Client | Result |
|
|
179
|
+
|---|---:|
|
|
180
|
+
| cloudflare | ✅ |
|
|
181
|
+
| craggy | ✅ |
|
|
182
|
+
| node-roughtime | ✅ |
|
|
183
|
+
| pyroughtime | ✅ |
|
|
184
|
+
| roughenough | ❌ |
|
|
185
|
+
| roughly | ✅ |
|
|
186
|
+
| vroughtime | ✅ |
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
### draft-7
|
|
192
|
+
|
|
193
|
+
Support for draft-7 is limited, in the sense that `roughly` will fit responses from draft-7 servers into the draft-15 data structures.
|
|
194
|
+
This means that some fields that are not present in draft-8+ (such as DUT1, DTAI, and LEAP) will be missing.
|
|
195
|
+
Additionally draft-7 offered for the precision of radius to be in microseconds, while draft-8+ uses seconds, this precision will be lost when querying draft-7 servers, and be clamped to a minimum of one second.
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "roughly"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "An asynchronous Roughtime implementation for Python"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
dependencies = ["cryptography>=46.0.3"]
|
|
10
|
+
keywords = ["roughtime", "time", "ntp", "synchronization", "asyncio"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python :: 3.12",
|
|
13
|
+
"Programming Language :: Python :: 3.13",
|
|
14
|
+
"Programming Language :: Python :: 3.14",
|
|
15
|
+
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Intended Audience :: Information Technology",
|
|
21
|
+
|
|
22
|
+
"Topic :: Internet",
|
|
23
|
+
"Topic :: Security :: Cryptography",
|
|
24
|
+
"Topic :: System :: Networking",
|
|
25
|
+
"Topic :: System :: Networking :: Time Synchronization",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
"Typing :: Typed",
|
|
28
|
+
"Framework :: AsyncIO",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
"Repository" = "https://github.com/teaishealthy/roughly"
|
|
33
|
+
"Issues" = "https://github.com/teaishealthy/roughly/issues"
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
cli = ["click>=8.3.1"]
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
roughly = "roughly.cli:cli"
|
|
40
|
+
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
dev = [
|
|
43
|
+
"pytest>=9.0.2",
|
|
44
|
+
"pytest-asyncio>=1.3.0",
|
|
45
|
+
"pytest-cov>=7.0.0",
|
|
46
|
+
"ruff>=0.14.10",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
include = ["roughly/*"]
|
|
51
|
+
line-length = 100
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint]
|
|
55
|
+
select = ["ALL"]
|
|
56
|
+
ignore = [
|
|
57
|
+
"A001", # variable shadowing a built-in
|
|
58
|
+
"A002", # argument shadowing a built-in
|
|
59
|
+
"ANN401", # no Any
|
|
60
|
+
|
|
61
|
+
"ASYNC109", # async function with timeout parameter
|
|
62
|
+
|
|
63
|
+
"TC006", # runtime-cast-value, i think it looks bad
|
|
64
|
+
"TRY003", # error messages in exceptions
|
|
65
|
+
|
|
66
|
+
"TD002",
|
|
67
|
+
"TD003",
|
|
68
|
+
"FIX002",
|
|
69
|
+
|
|
70
|
+
# these are disabled because i can't configure what 'public' means
|
|
71
|
+
"D100", # missing docstring in public module
|
|
72
|
+
"D101", # missing docstring in public class
|
|
73
|
+
"D102", # missing docstring in public method
|
|
74
|
+
"D103", # missing docstring in public function
|
|
75
|
+
"D105", # missing docstring in magic method
|
|
76
|
+
"D107", # missing docstring in __init__
|
|
77
|
+
|
|
78
|
+
# disabled because of `ruff format`
|
|
79
|
+
"COM812",
|
|
80
|
+
"ISC001",
|
|
81
|
+
|
|
82
|
+
"EM101", # use of string literals in exceptions
|
|
83
|
+
"EM102", # use of f-string literals in exceptions
|
|
84
|
+
|
|
85
|
+
"SLF001", # private member access
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
[tool.ruff.lint.per-file-ignores]
|
|
89
|
+
"tests/**/*" = [
|
|
90
|
+
"INP001", # implicit namespace packeges are "ok"
|
|
91
|
+
"S101", # allow asserts
|
|
92
|
+
"PLR2004", # magic numbers in tests are fine
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[tool.ruff.lint.pydocstyle]
|
|
96
|
+
convention = "google" # Accepts: "google", "numpy", or "pep257".
|
|
97
|
+
|
|
98
|
+
[tool.uv.build-backend]
|
|
99
|
+
module-root = ""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
[tool.coverage.report]
|
|
103
|
+
exclude_also = ["pragma: no cover", "if TYPE_CHECKING:"]
|
|
104
|
+
|
|
105
|
+
[tool.pytest.ini_options]
|
|
106
|
+
log_cli = true
|
|
107
|
+
log_cli_level = "DEBUG"
|
|
108
|
+
|
|
109
|
+
[build-system]
|
|
110
|
+
requires = ["uv_build>=0.9.27,<0.10.0"]
|
|
111
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Roughtime protocol implementation in Python."""
|