generic_time_series_objects 0.1.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.
- generic_time_series_objects-0.1.1/.gitignore +1 -0
- generic_time_series_objects-0.1.1/.idea/.gitignore +8 -0
- generic_time_series_objects-0.1.1/.idea/markdown.xml +6 -0
- generic_time_series_objects-0.1.1/.idea/misc.xml +6 -0
- generic_time_series_objects-0.1.1/.idea/modules.xml +8 -0
- generic_time_series_objects-0.1.1/.idea/rust-time-series-objects.iml +17 -0
- generic_time_series_objects-0.1.1/.idea/vcs.xml +6 -0
- generic_time_series_objects-0.1.1/.pypirc +3 -0
- generic_time_series_objects-0.1.1/Cargo.lock +172 -0
- generic_time_series_objects-0.1.1/Cargo.toml +15 -0
- generic_time_series_objects-0.1.1/PKG-INFO +6 -0
- generic_time_series_objects-0.1.1/README.md +257 -0
- generic_time_series_objects-0.1.1/pyproject.toml +16 -0
- generic_time_series_objects-0.1.1/python/generic_time_series_objects/__init__.py +17 -0
- generic_time_series_objects-0.1.1/python/generic_time_series_objects/test.py +8 -0
- generic_time_series_objects-0.1.1/python/generic_time_series_objects/time_series_data_baseclass.py +49 -0
- generic_time_series_objects-0.1.1/python/test_ts.py +131 -0
- generic_time_series_objects-0.1.1/python/test_ts_data_class.py +43 -0
- generic_time_series_objects-0.1.1/src/lib.rs +168 -0
- generic_time_series_objects-0.1.1/uv.lock +75 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/target
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="ProjectModuleManager">
|
|
4
|
+
<modules>
|
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/rust-time-series-objects.iml" filepath="$PROJECT_DIR$/.idea/rust-time-series-objects.iml" />
|
|
6
|
+
</modules>
|
|
7
|
+
</component>
|
|
8
|
+
</project>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="EMPTY_MODULE" version="4">
|
|
3
|
+
<component name="FacetManager">
|
|
4
|
+
<facet type="Python" name="Python facet">
|
|
5
|
+
<configuration sdkName="Python 3.13" />
|
|
6
|
+
</facet>
|
|
7
|
+
</component>
|
|
8
|
+
<component name="NewModuleRootManager">
|
|
9
|
+
<content url="file://$MODULE_DIR$">
|
|
10
|
+
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
11
|
+
<excludeFolder url="file://$MODULE_DIR$/target" />
|
|
12
|
+
</content>
|
|
13
|
+
<orderEntry type="inheritedJdk" />
|
|
14
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
15
|
+
<orderEntry type="library" name="Python 3.13 interpreter library" level="application" />
|
|
16
|
+
</component>
|
|
17
|
+
</module>
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "autocfg"
|
|
7
|
+
version = "1.5.0"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "generic_time_series_objects"
|
|
13
|
+
version = "0.1.0"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"pyo3",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[[package]]
|
|
19
|
+
name = "heck"
|
|
20
|
+
version = "0.5.0"
|
|
21
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
22
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
23
|
+
|
|
24
|
+
[[package]]
|
|
25
|
+
name = "indoc"
|
|
26
|
+
version = "2.0.7"
|
|
27
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
28
|
+
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
|
29
|
+
dependencies = [
|
|
30
|
+
"rustversion",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[[package]]
|
|
34
|
+
name = "libc"
|
|
35
|
+
version = "0.2.177"
|
|
36
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
37
|
+
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
|
38
|
+
|
|
39
|
+
[[package]]
|
|
40
|
+
name = "memoffset"
|
|
41
|
+
version = "0.9.1"
|
|
42
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
43
|
+
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
|
44
|
+
dependencies = [
|
|
45
|
+
"autocfg",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[[package]]
|
|
49
|
+
name = "once_cell"
|
|
50
|
+
version = "1.21.3"
|
|
51
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
52
|
+
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
|
53
|
+
|
|
54
|
+
[[package]]
|
|
55
|
+
name = "portable-atomic"
|
|
56
|
+
version = "1.11.1"
|
|
57
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
58
|
+
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
|
59
|
+
|
|
60
|
+
[[package]]
|
|
61
|
+
name = "proc-macro2"
|
|
62
|
+
version = "1.0.103"
|
|
63
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
64
|
+
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
|
65
|
+
dependencies = [
|
|
66
|
+
"unicode-ident",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[[package]]
|
|
70
|
+
name = "pyo3"
|
|
71
|
+
version = "0.27.2"
|
|
72
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
73
|
+
checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d"
|
|
74
|
+
dependencies = [
|
|
75
|
+
"indoc",
|
|
76
|
+
"libc",
|
|
77
|
+
"memoffset",
|
|
78
|
+
"once_cell",
|
|
79
|
+
"portable-atomic",
|
|
80
|
+
"pyo3-build-config",
|
|
81
|
+
"pyo3-ffi",
|
|
82
|
+
"pyo3-macros",
|
|
83
|
+
"unindent",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
[[package]]
|
|
87
|
+
name = "pyo3-build-config"
|
|
88
|
+
version = "0.27.2"
|
|
89
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
90
|
+
checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6"
|
|
91
|
+
dependencies = [
|
|
92
|
+
"target-lexicon",
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[[package]]
|
|
96
|
+
name = "pyo3-ffi"
|
|
97
|
+
version = "0.27.2"
|
|
98
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
99
|
+
checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089"
|
|
100
|
+
dependencies = [
|
|
101
|
+
"libc",
|
|
102
|
+
"pyo3-build-config",
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
[[package]]
|
|
106
|
+
name = "pyo3-macros"
|
|
107
|
+
version = "0.27.2"
|
|
108
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
109
|
+
checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02"
|
|
110
|
+
dependencies = [
|
|
111
|
+
"proc-macro2",
|
|
112
|
+
"pyo3-macros-backend",
|
|
113
|
+
"quote",
|
|
114
|
+
"syn",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
[[package]]
|
|
118
|
+
name = "pyo3-macros-backend"
|
|
119
|
+
version = "0.27.2"
|
|
120
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
121
|
+
checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9"
|
|
122
|
+
dependencies = [
|
|
123
|
+
"heck",
|
|
124
|
+
"proc-macro2",
|
|
125
|
+
"pyo3-build-config",
|
|
126
|
+
"quote",
|
|
127
|
+
"syn",
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
[[package]]
|
|
131
|
+
name = "quote"
|
|
132
|
+
version = "1.0.41"
|
|
133
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
134
|
+
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
|
135
|
+
dependencies = [
|
|
136
|
+
"proc-macro2",
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
[[package]]
|
|
140
|
+
name = "rustversion"
|
|
141
|
+
version = "1.0.22"
|
|
142
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
143
|
+
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
144
|
+
|
|
145
|
+
[[package]]
|
|
146
|
+
name = "syn"
|
|
147
|
+
version = "2.0.108"
|
|
148
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
149
|
+
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
|
|
150
|
+
dependencies = [
|
|
151
|
+
"proc-macro2",
|
|
152
|
+
"quote",
|
|
153
|
+
"unicode-ident",
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
[[package]]
|
|
157
|
+
name = "target-lexicon"
|
|
158
|
+
version = "0.13.3"
|
|
159
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
160
|
+
checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
|
|
161
|
+
|
|
162
|
+
[[package]]
|
|
163
|
+
name = "unicode-ident"
|
|
164
|
+
version = "1.0.20"
|
|
165
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
166
|
+
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
|
|
167
|
+
|
|
168
|
+
[[package]]
|
|
169
|
+
name = "unindent"
|
|
170
|
+
version = "0.2.4"
|
|
171
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
172
|
+
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "generic_time_series_objects"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2024"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
|
|
7
|
+
[lib]
|
|
8
|
+
name = "generic_time_series_objects"
|
|
9
|
+
crate-type = ["cdylib"]
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
|
|
13
|
+
[dependencies.pyo3]
|
|
14
|
+
version = "0.27.0"
|
|
15
|
+
features = ["abi3-py38"]
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# [Generic Time Series Objects](https://pypi.org/project/generic-time-series-objects/)
|
|
2
|
+
Store Python objects in a time series to capture evolving data over time. Built to be highly
|
|
3
|
+
generic and capable of storing any python class, even custom, against a timestamp (integer)
|
|
4
|
+
value. This project is built in rust, with [pyo3](https://github.com/PyO3/pyo3) bindings, and
|
|
5
|
+
compiled using [maturin](https://github.com/PyO3/maturin). Tests are written in python
|
|
6
|
+
(with pytest).
|
|
7
|
+
|
|
8
|
+
> [!NOTE]
|
|
9
|
+
>
|
|
10
|
+
> Rust code is compiled using `maturin develop --uv` (or `maturin develop --uv --release`). <br>
|
|
11
|
+
> Test cases are then run using `pytest .\python\test_ts.py`.
|
|
12
|
+
|
|
13
|
+
> [!IMPORTANT]
|
|
14
|
+
>
|
|
15
|
+
> Work in progress:
|
|
16
|
+
> - [ ] Time Series Data BaseClass to manage methods as timeseries that can change through time.
|
|
17
|
+
> - [ ] Add further tests to `test_ts.py`
|
|
18
|
+
> - [ ] Proper set up of `test_ts_data_class.py`
|
|
19
|
+
> - [ ] Migrate the Time Series Data BaseClass into `lib.rs`
|
|
20
|
+
|
|
21
|
+
## TimeSeriesObject Interface
|
|
22
|
+
Methods to interact with the TimeSeriesObject Class.
|
|
23
|
+
### Dunder Methods
|
|
24
|
+
#### __new\_\_
|
|
25
|
+
Creates the object with no arguments. <br>
|
|
26
|
+
_Arguments_
|
|
27
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
28
|
+
|
|
29
|
+
_Output/Exceptions_
|
|
30
|
+
* (`TimeSeriesObject`): Returns the created object.
|
|
31
|
+
|
|
32
|
+
_Example:_
|
|
33
|
+
```python
|
|
34
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
35
|
+
|
|
36
|
+
obj = TimeSeriesObject()
|
|
37
|
+
```
|
|
38
|
+
#### __repr\_\_
|
|
39
|
+
Representation of the object. <br>
|
|
40
|
+
_Arguments_
|
|
41
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
42
|
+
|
|
43
|
+
_Output/Exceptions_
|
|
44
|
+
* (`str`): Returns a string with the object name and a list of timestamps.
|
|
45
|
+
|
|
46
|
+
_Example:_
|
|
47
|
+
```python
|
|
48
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
49
|
+
|
|
50
|
+
obj = TimeSeriesObject()
|
|
51
|
+
print(obj) # prints "TimeSeriesObject(timestamps=[])"
|
|
52
|
+
```
|
|
53
|
+
#### __len\_\_
|
|
54
|
+
Returns the number of data points currently stored in the time series object. <br>
|
|
55
|
+
_Arguments_
|
|
56
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
57
|
+
|
|
58
|
+
_Output/Exceptions_
|
|
59
|
+
* (`int`): Number of data points inserted into the object.
|
|
60
|
+
|
|
61
|
+
_Example:_
|
|
62
|
+
```python
|
|
63
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
64
|
+
|
|
65
|
+
obj = TimeSeriesObject()
|
|
66
|
+
print(len(obj)) # prints 0
|
|
67
|
+
```
|
|
68
|
+
#### __bool\_\_
|
|
69
|
+
Returns a boolean for if the object contains data points or not. <br>
|
|
70
|
+
_Arguments_
|
|
71
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
72
|
+
|
|
73
|
+
_Output/Exceptions_
|
|
74
|
+
* (`bool`): False if there are no data points, otherwise True.
|
|
75
|
+
|
|
76
|
+
_Example:_
|
|
77
|
+
```python
|
|
78
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
79
|
+
|
|
80
|
+
obj = TimeSeriesObject()
|
|
81
|
+
print(bool(obj)) # prints False
|
|
82
|
+
```
|
|
83
|
+
### Mutating Data
|
|
84
|
+
Methods return None for success and raises Exception if failed to perform operation.
|
|
85
|
+
#### insert
|
|
86
|
+
Inserts a Python object at a given timestamp. <br>
|
|
87
|
+
_Arguments_
|
|
88
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
89
|
+
* **`ts`** (`int`): Timestamp of the data point.
|
|
90
|
+
* **`value`** (`Any`): The Python object to be stored.
|
|
91
|
+
* **`overwrite`** (`bool`): Defaults to `False`. Determines what to do if a provided timestamp already exists, if
|
|
92
|
+
`overwrite=False`, raises Exception otherwise overwrites the existing data point.
|
|
93
|
+
|
|
94
|
+
_Output/Exceptions_
|
|
95
|
+
* (`None`): Successfully inserted data point at timestamp.
|
|
96
|
+
* **`ValueError`** (`Exception`): Timestamp provided already has existing data point and overwrite is set to False.
|
|
97
|
+
|
|
98
|
+
_Example:_
|
|
99
|
+
```python
|
|
100
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
101
|
+
|
|
102
|
+
obj = TimeSeriesObject()
|
|
103
|
+
obj.insert(1, {1, 2, 3}) # overwrite defaults to False
|
|
104
|
+
# obj.insert(1, {1, 2, 3}) # !raises ValueError
|
|
105
|
+
obj.insert(1, {1, 2, 3}, overwrite=True)
|
|
106
|
+
```
|
|
107
|
+
#### update
|
|
108
|
+
Updates the point at a given timestamp. <br>
|
|
109
|
+
_Arguments_
|
|
110
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
111
|
+
* **`ts`** (`int`): Timestamp of the point we want to update.
|
|
112
|
+
* **`value`** (`Any`): The Python object we want to update with.
|
|
113
|
+
|
|
114
|
+
_Output/Exceptions_
|
|
115
|
+
* (`None`): Successfully inserted Python object at timestamp.
|
|
116
|
+
* **`ValueError`** (`Exception`): TimeSeriesObject is empty and could not update.
|
|
117
|
+
* **`IndexError`** (`Exception`): Provided timestamp does not exist within TimeSeriesObject.
|
|
118
|
+
|
|
119
|
+
_Example:_
|
|
120
|
+
```python
|
|
121
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
122
|
+
|
|
123
|
+
obj = TimeSeriesObject()
|
|
124
|
+
# obj.update(2, {1, 2, 3, 4}) # !raises ValueError
|
|
125
|
+
obj.insert(1, {1, 2, 3})
|
|
126
|
+
obj.update(1, {1, 2, 3, 4})
|
|
127
|
+
# obj.update(2, {1, 2, 3, 4}) # !raises IndexError
|
|
128
|
+
```
|
|
129
|
+
#### delete
|
|
130
|
+
Deletes the data point at a given timestamp. <br>
|
|
131
|
+
_Arguments_
|
|
132
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
133
|
+
* **`ts`** (`int`): Timestamp of the point we want to delete.
|
|
134
|
+
|
|
135
|
+
_Output/Exceptions_
|
|
136
|
+
* (`None`): Successfully deleted data point.
|
|
137
|
+
* **`ValueError`** (`Exception`): TimeSeriesObject is empty and could not delete.
|
|
138
|
+
* **`IndexError`** (`Exception`): Provided timestamp does not exist within TimeSeriesObject.
|
|
139
|
+
|
|
140
|
+
_Example:_
|
|
141
|
+
```python
|
|
142
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
143
|
+
|
|
144
|
+
obj = TimeSeriesObject()
|
|
145
|
+
# obj.delete(2) # !raises ValueError
|
|
146
|
+
obj.insert(1, {1, 2, 3})
|
|
147
|
+
# obj.delete(2) # !raises IndexError
|
|
148
|
+
obj.delete(1)
|
|
149
|
+
```
|
|
150
|
+
### Retrieving Data Points
|
|
151
|
+
Methods return a tuple of the timestamp and Python object for success and None if nothing is found.
|
|
152
|
+
#### point
|
|
153
|
+
Fetches the data point on or before a certain timestamp.<br>
|
|
154
|
+
_Arguments_
|
|
155
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
156
|
+
* **`ts`** (`int`): Timestamp on or before the time we want to retrieve data for.
|
|
157
|
+
|
|
158
|
+
_Output/Exceptions_
|
|
159
|
+
* (`tuple[int, Any]`): The data point, as a tuple of timestamp and Python object, that was retrieved.
|
|
160
|
+
* **`None`** (`NoneType`): Nothing was found, in this case timestamp provided was before the minimum timestamp in the
|
|
161
|
+
TimeSeriesObject.
|
|
162
|
+
|
|
163
|
+
_Example:_
|
|
164
|
+
```python
|
|
165
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
166
|
+
|
|
167
|
+
obj = TimeSeriesObject()
|
|
168
|
+
obj.insert(2, {1, 2, 3})
|
|
169
|
+
obj.insert(10, {1, 2, 3, 4})
|
|
170
|
+
print(obj.point(10)) # prints {1, 2, 3, 4}
|
|
171
|
+
print(obj.point(5)) # prints {1, 2, 3}
|
|
172
|
+
print(obj.point(1)) # prints None
|
|
173
|
+
```
|
|
174
|
+
#### point_on
|
|
175
|
+
Fetches the data point exactly on a certain timestamp.<br>
|
|
176
|
+
_Arguments_
|
|
177
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
178
|
+
* **`ts`** (`int`): Timestamp exactly equal to the time we want to retrieve data for.
|
|
179
|
+
|
|
180
|
+
_Output/Exceptions_
|
|
181
|
+
* (`tuple[int, Any]`): The data point, as a tuple of timestamp and Python object, that was retrieved.
|
|
182
|
+
* **`None`** (`NoneType`): Nothing was found at provided timestamp.
|
|
183
|
+
|
|
184
|
+
_Example:_
|
|
185
|
+
```python
|
|
186
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
187
|
+
|
|
188
|
+
obj = TimeSeriesObject()
|
|
189
|
+
obj.insert(2, {1, 2, 3})
|
|
190
|
+
obj.insert(10, {1, 2, 3, 4})
|
|
191
|
+
print(obj.point_on(10)) # prints {1, 2, 3, 4}
|
|
192
|
+
print(obj.point_on(5)) # prints None
|
|
193
|
+
print(obj.point_on(2)) # prints {1, 2, 3}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### points_between
|
|
197
|
+
Fetches all data points between the two provided timestamps, inclusive of start and exclusive of end [start_ts, end_ts).
|
|
198
|
+
<br>
|
|
199
|
+
_Arguments_
|
|
200
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
201
|
+
* **`start_ts`** (`int`): Start timestamp to filter for, inclusive.
|
|
202
|
+
* **`end_ts`** (`int`): End timestamp to filter for, exclusive.
|
|
203
|
+
|
|
204
|
+
_Output/Exceptions_
|
|
205
|
+
* (`list[tuple[int, Any]]`): List of points between the starting and ending timestamp.
|
|
206
|
+
|
|
207
|
+
_Example:_
|
|
208
|
+
```python
|
|
209
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
210
|
+
|
|
211
|
+
obj = TimeSeriesObject()
|
|
212
|
+
obj.insert(2, {1})
|
|
213
|
+
obj.insert(5, {1, 2})
|
|
214
|
+
obj.insert(10, {1, 2, 3})
|
|
215
|
+
print(obj.points_between(1, 100)) # prints [(2, {1}), (5, {1, 2}), (10, {1, 2, 3})]
|
|
216
|
+
print(obj.points_between(1, 10)) # prints [(2, {1}), (5, {1, 2})]
|
|
217
|
+
print(obj.points_between(1, 1)) # prints []
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
### Transforming Data Type
|
|
221
|
+
Methods return the data type named in the method as the outer return type.
|
|
222
|
+
#### as_dict
|
|
223
|
+
Transforms all data points in the TimeSeriesObject to a mapping between the timestamp and the Python object.<br>
|
|
224
|
+
_Arguments_
|
|
225
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
226
|
+
|
|
227
|
+
_Output/Exceptions_
|
|
228
|
+
* (`dict[int, Any]`): All data points in the form of a dictionary mapping timestamp to Python
|
|
229
|
+
object.
|
|
230
|
+
|
|
231
|
+
_Example:_
|
|
232
|
+
```python
|
|
233
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
234
|
+
|
|
235
|
+
obj = TimeSeriesObject()
|
|
236
|
+
print(obj.as_dict()) # prints {}
|
|
237
|
+
obj.insert(1, ['hello'])
|
|
238
|
+
print(obj.as_dict()) # prints {1: ['hello']}
|
|
239
|
+
```
|
|
240
|
+
#### as_list
|
|
241
|
+
Transforms all data points in the TimeSeriesObject to a list of tuples containing the timestamp and the Python object.<br>
|
|
242
|
+
_Arguments_
|
|
243
|
+
* **`self`** (`TimeSeriesObject`): The object itself.
|
|
244
|
+
|
|
245
|
+
_Output/Exceptions_
|
|
246
|
+
* (`list[tuple[int, Any]]`): All data points in the form of a list of tuples with each tuple
|
|
247
|
+
containing a timestamp and the Python object.
|
|
248
|
+
|
|
249
|
+
_Example:_
|
|
250
|
+
```python
|
|
251
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
252
|
+
|
|
253
|
+
obj = TimeSeriesObject()
|
|
254
|
+
print(obj.as_list()) # prints []
|
|
255
|
+
obj.insert(1, ['hello'])
|
|
256
|
+
print(obj.as_list()) # prints [(1, ['hello'])]
|
|
257
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "generic_time_series_objects"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Generic Time Series Objects for Python"
|
|
5
|
+
requires-python = ">=3.13"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"pytest>=8.4.2",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["maturin>=1.0,<2.0"]
|
|
12
|
+
build-backend = "maturin"
|
|
13
|
+
|
|
14
|
+
[tool.maturin]
|
|
15
|
+
features = ["pyo3/extension-module"]
|
|
16
|
+
python-source = "python"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .generic_time_series_objects import TimeSeriesObject
|
|
2
|
+
from .time_series_data_baseclass import TimeSeriesDataBaseclass, time_series_data
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
classes = [
|
|
6
|
+
"TimeSeriesObject",
|
|
7
|
+
"TimeSeriesDataBaseclass",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
decorators = [
|
|
11
|
+
"time_series_data",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
*classes,
|
|
16
|
+
*decorators,
|
|
17
|
+
]
|
generic_time_series_objects-0.1.1/python/generic_time_series_objects/time_series_data_baseclass.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
TS_DATA_FLAG = 'is_time_series_data'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TimeSeriesDataBaseclass(ABC):
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.timestamp: int = -1
|
|
15
|
+
self.data: dict[str, TimeSeriesObject] = {}
|
|
16
|
+
ts_data_flag = TS_DATA_FLAG
|
|
17
|
+
for method_name in dir(self):
|
|
18
|
+
method = getattr(self, method_name)
|
|
19
|
+
if not getattr(method, ts_data_flag, False):
|
|
20
|
+
continue
|
|
21
|
+
self.data[method_name] = TimeSeriesObject()
|
|
22
|
+
|
|
23
|
+
def set_date(self, new_date: datetime.datetime) -> None:
|
|
24
|
+
self.timestamp = int(new_date.timestamp())
|
|
25
|
+
|
|
26
|
+
def reset_data(self) -> None:
|
|
27
|
+
self.timestamp = -1
|
|
28
|
+
|
|
29
|
+
def update(self, new_data: dict[str, Any], overwrite: bool=False) -> None:
|
|
30
|
+
if self.timestamp == -1:
|
|
31
|
+
raise ValueError("`set_date` before attempting to `update`")
|
|
32
|
+
for method_name, data_point in new_data.items():
|
|
33
|
+
self.data[method_name].insert(self.timestamp, data_point, overwrite)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def time_series_data(fn: Callable) -> Callable:
|
|
37
|
+
fn.is_time_series_data = True
|
|
38
|
+
@wraps(fn)
|
|
39
|
+
def wrapper(self, *args, **kwargs) -> Any:
|
|
40
|
+
if self.timestamp == -1:
|
|
41
|
+
return fn(self, *args, **kwargs)
|
|
42
|
+
|
|
43
|
+
data_point = self.data[fn.__name__].point(self.timestamp)
|
|
44
|
+
if not data_point:
|
|
45
|
+
return fn(self, *args, **kwargs)
|
|
46
|
+
|
|
47
|
+
return data_point[1]
|
|
48
|
+
|
|
49
|
+
return wrapper
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from generic_time_series_objects import TimeSeriesObject
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
TEST_INIT_POINTS = [
|
|
7
|
+
[1, 2, 3, 4, 5, 10],
|
|
8
|
+
[-1.0, -7.5, -100.1, -50, -1000],
|
|
9
|
+
[3, -1, -50.5, "X", ["OOPS"], {1, 2, 3, 4}, {"TEST": "TEST1"}, lambda x: x, ("1", True), None],
|
|
10
|
+
range(1, 1_000_000),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TEST_POINT_DATA = [
|
|
14
|
+
([1, 5, 10, 15], [10, 20, 30, 40]),
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.parametrize("test_points", TEST_INIT_POINTS)
|
|
19
|
+
def test_initialise_ts_object(test_points: list):
|
|
20
|
+
obj = TimeSeriesObject()
|
|
21
|
+
assert obj is not None
|
|
22
|
+
for i, val in enumerate(test_points):
|
|
23
|
+
obj.insert(i, val)
|
|
24
|
+
|
|
25
|
+
for i, val in enumerate(test_points):
|
|
26
|
+
point_time, point_value = obj.point(i)
|
|
27
|
+
assert point_time == i
|
|
28
|
+
assert point_value == val
|
|
29
|
+
assert len(obj) == len(test_points)
|
|
30
|
+
|
|
31
|
+
assert obj.as_dict() == {i: val for i, val in enumerate(test_points)}
|
|
32
|
+
assert obj.as_list() == [(i, val) for i, val in enumerate(test_points)]
|
|
33
|
+
with pytest.raises(AttributeError):
|
|
34
|
+
obj.get_insertion_index(3)
|
|
35
|
+
with pytest.raises(AttributeError):
|
|
36
|
+
obj.is_empty()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_empty_object():
|
|
40
|
+
obj = TimeSeriesObject()
|
|
41
|
+
assert obj is not None
|
|
42
|
+
assert obj.as_dict() == {}
|
|
43
|
+
assert obj.as_list() == []
|
|
44
|
+
assert obj.point(0) is None
|
|
45
|
+
assert obj.points_between(0, 1) == []
|
|
46
|
+
with pytest.raises(ValueError):
|
|
47
|
+
obj.update(100, None)
|
|
48
|
+
with pytest.raises(ValueError):
|
|
49
|
+
obj.delete(100)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.parametrize(("test_ts", "test_points"), TEST_POINT_DATA)
|
|
53
|
+
def test_ts_object_point(test_ts: list, test_points: list):
|
|
54
|
+
obj = TimeSeriesObject()
|
|
55
|
+
for ts, val in zip(test_ts, test_points):
|
|
56
|
+
obj.insert(ts, val)
|
|
57
|
+
|
|
58
|
+
loop_test_ts = test_ts.copy()
|
|
59
|
+
loop_test_points = test_points.copy()
|
|
60
|
+
for i in range(0, max(test_ts)+1):
|
|
61
|
+
if i < test_ts[0]:
|
|
62
|
+
assert obj.point(i) is None
|
|
63
|
+
continue
|
|
64
|
+
point_time, point_value = obj.point(i)
|
|
65
|
+
if i == loop_test_ts[1]:
|
|
66
|
+
loop_test_ts = loop_test_ts[1:]
|
|
67
|
+
loop_test_points = loop_test_points[1:]
|
|
68
|
+
assert point_time >= loop_test_ts[0]
|
|
69
|
+
assert point_value == loop_test_points[0]
|
|
70
|
+
|
|
71
|
+
assert obj.point(100000) == (test_ts[-1], test_points[-1])
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.parametrize(("test_ts", "test_points"), TEST_POINT_DATA)
|
|
75
|
+
def test_ts_object_point_on(test_ts: list, test_points: list):
|
|
76
|
+
obj = TimeSeriesObject()
|
|
77
|
+
for ts, val in zip(test_ts, test_points):
|
|
78
|
+
obj.insert(ts, val)
|
|
79
|
+
|
|
80
|
+
for i in range(0, max(test_ts)+1):
|
|
81
|
+
if i not in test_ts:
|
|
82
|
+
assert obj.point_on(i) is None
|
|
83
|
+
continue
|
|
84
|
+
point_time, point_value = obj.point_on(i)
|
|
85
|
+
assert point_time in test_ts
|
|
86
|
+
assert point_value in test_points
|
|
87
|
+
|
|
88
|
+
assert obj.point_on(100000) is None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.mark.parametrize(("test_ts", "test_points"), TEST_POINT_DATA)
|
|
92
|
+
def test_ts_points_between(test_ts: list, test_points: list):
|
|
93
|
+
obj = TimeSeriesObject()
|
|
94
|
+
for ts, val in zip(test_ts, test_points):
|
|
95
|
+
obj.insert(ts, val)
|
|
96
|
+
assert [x for _, x in obj.points_between(0, 1_000_000)] == [10, 20, 30, 40]
|
|
97
|
+
assert [x for _, x in obj.points_between(1, 16)] == [10, 20, 30, 40]
|
|
98
|
+
assert [x for _, x in obj.points_between(1, 15)] == [10, 20, 30]
|
|
99
|
+
assert [x for _, x in obj.points_between(1, 14)] == [10, 20, 30]
|
|
100
|
+
assert [x for _, x in obj.points_between(2, 12)] == [20, 30]
|
|
101
|
+
assert [x for _, x in obj.points_between(2, 11)] == [20, 30]
|
|
102
|
+
assert [x for _, x in obj.points_between(1, 10)] == [10, 20]
|
|
103
|
+
assert [x for _, x in obj.points_between(0, 0)] == []
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.mark.parametrize(("test_ts", "test_points"), TEST_POINT_DATA)
|
|
107
|
+
def test_update_delete_from_object(test_ts: list, test_points: list):
|
|
108
|
+
obj = TimeSeriesObject()
|
|
109
|
+
for ts, val in zip(test_ts, test_points):
|
|
110
|
+
obj.insert(ts, val)
|
|
111
|
+
# cannot add same thing twice
|
|
112
|
+
with pytest.raises(ValueError):
|
|
113
|
+
obj.insert(test_ts[0], test_points[0])
|
|
114
|
+
|
|
115
|
+
obj.insert(test_ts[0], test_points[0], overwrite=True)
|
|
116
|
+
|
|
117
|
+
# check index must exist for update
|
|
118
|
+
with pytest.raises(IndexError):
|
|
119
|
+
obj.update(0, None)
|
|
120
|
+
|
|
121
|
+
obj.update(test_ts[0], None)
|
|
122
|
+
|
|
123
|
+
# check can delete
|
|
124
|
+
with pytest.raises(IndexError):
|
|
125
|
+
obj.delete(0)
|
|
126
|
+
|
|
127
|
+
for ts in test_ts:
|
|
128
|
+
obj.delete(ts)
|
|
129
|
+
|
|
130
|
+
with pytest.raises(ValueError):
|
|
131
|
+
obj.delete(test_ts[0])
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# import pytest
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
from generic_time_series_objects import TimeSeriesDataBaseclass, time_series_data
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestClass(TimeSeriesDataBaseclass):
|
|
8
|
+
|
|
9
|
+
def __init__(self, a, b, c):
|
|
10
|
+
super().__init__()
|
|
11
|
+
self.a = a
|
|
12
|
+
self.b = b
|
|
13
|
+
self.c = c
|
|
14
|
+
|
|
15
|
+
@time_series_data
|
|
16
|
+
def A(self):
|
|
17
|
+
return self.a
|
|
18
|
+
|
|
19
|
+
@time_series_data
|
|
20
|
+
def B(self):
|
|
21
|
+
return self.b
|
|
22
|
+
|
|
23
|
+
@time_series_data
|
|
24
|
+
def C(self):
|
|
25
|
+
return self.c
|
|
26
|
+
|
|
27
|
+
@time_series_data
|
|
28
|
+
def D(self):
|
|
29
|
+
return 1
|
|
30
|
+
|
|
31
|
+
def E(self):
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
test_obj = TestClass(2, 3, 4)
|
|
35
|
+
print(test_obj.A(), test_obj.B(), test_obj.C(), test_obj.D())
|
|
36
|
+
test_obj.set_date(datetime.datetime(2025, 12, 13))
|
|
37
|
+
test_obj.update({"A": 9, "B": 99, "C": 999})
|
|
38
|
+
print(test_obj.A(), test_obj.B(), test_obj.C(), test_obj.D())
|
|
39
|
+
test_obj.reset_data()
|
|
40
|
+
print(test_obj.A(), test_obj.B(), test_obj.C(), test_obj.D())
|
|
41
|
+
test_obj.set_date(datetime.datetime(2025, 12, 14))
|
|
42
|
+
print(test_obj.A(), test_obj.B(), test_obj.C(), test_obj.D())
|
|
43
|
+
print(test_obj.data)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
|
|
3
|
+
use pyo3::exceptions::{PyValueError, PyIndexError};
|
|
4
|
+
use pyo3::prelude::*;
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
#[pyclass]
|
|
8
|
+
struct TimeSeriesObject {
|
|
9
|
+
timestamps: Vec<i32>,
|
|
10
|
+
values: Vec<Py<PyAny>>,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
impl TimeSeriesObject {
|
|
15
|
+
fn get_insertion_index(&self, ts: i32) -> usize {
|
|
16
|
+
self.timestamps.binary_search(&ts).unwrap_or_else(|idx| idx)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
fn is_empty(&self) -> bool {
|
|
20
|
+
self.timestamps.is_empty()
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
#[pymethods]
|
|
26
|
+
impl TimeSeriesObject {
|
|
27
|
+
|
|
28
|
+
#[new]
|
|
29
|
+
fn new() -> Self {
|
|
30
|
+
TimeSeriesObject {timestamps: Vec::new(), values: Vec::new()}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn __len__(&self) -> usize {
|
|
34
|
+
self.timestamps.len()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn __repr__(&self) -> String {
|
|
38
|
+
let timestamps = &self.timestamps;
|
|
39
|
+
format!("TimeSeriesObject(timestamps={timestamps:?})")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn __bool__(&self) -> bool {
|
|
43
|
+
!self.is_empty()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#[pyo3(signature = (ts, value, overwrite = false))]
|
|
47
|
+
fn insert(&mut self, ts: i32, value: Py<PyAny>, overwrite: bool) -> PyResult<()> {
|
|
48
|
+
if self.is_empty() || (ts > self.timestamps[self.timestamps.len()-1]) {
|
|
49
|
+
self.timestamps.push(ts);
|
|
50
|
+
self.values.push(value);
|
|
51
|
+
return Ok(())
|
|
52
|
+
}
|
|
53
|
+
let idx = self.get_insertion_index(ts);
|
|
54
|
+
|
|
55
|
+
let current_ts_at_idx = self.timestamps[idx];
|
|
56
|
+
|
|
57
|
+
if (current_ts_at_idx == ts) & overwrite{
|
|
58
|
+
self.values[idx] = value;
|
|
59
|
+
return Ok(())
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (current_ts_at_idx == ts) & !overwrite{
|
|
63
|
+
return Err(PyValueError::new_err("Timestamp already exists in TimeSeriesObject, set overwrite=True to overwrite this value"))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
self.timestamps.insert(idx, ts);
|
|
67
|
+
self.values.insert(idx, value);
|
|
68
|
+
Ok(())
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn delete(&mut self, ts: i32) -> PyResult<()> {
|
|
72
|
+
if self.is_empty() {
|
|
73
|
+
return Err(PyValueError::new_err("TimeSeriesObject is empty"))
|
|
74
|
+
}
|
|
75
|
+
let delete_idx = self.get_insertion_index(ts);
|
|
76
|
+
if self.timestamps[delete_idx] != ts {
|
|
77
|
+
return Err(PyIndexError::new_err("Timestamp does not exist in TimeSeriesObject"))
|
|
78
|
+
}
|
|
79
|
+
self.timestamps.remove(delete_idx);
|
|
80
|
+
self.values.remove(delete_idx);
|
|
81
|
+
Ok(())
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn update(&mut self, ts: i32, value: Py<PyAny>) -> PyResult<()> {
|
|
85
|
+
if self.is_empty() {
|
|
86
|
+
return Err(PyValueError::new_err("TimeSeriesObject is empty"))
|
|
87
|
+
}
|
|
88
|
+
let update_idx = self.get_insertion_index(ts);
|
|
89
|
+
if self.timestamps[update_idx] != ts {
|
|
90
|
+
return Err(PyIndexError::new_err("Timestamp does not exist in TimeSeriesObject"))
|
|
91
|
+
}
|
|
92
|
+
self.values[update_idx] = value;
|
|
93
|
+
Ok(())
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn point(&self, ts: i32) -> Option<(&i32, &Py<PyAny>)> {
|
|
97
|
+
if self.is_empty() {
|
|
98
|
+
return None
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let idx = self.get_insertion_index(ts);
|
|
102
|
+
if idx == 0 && self.timestamps[idx] == ts {
|
|
103
|
+
return Some((&self.timestamps[idx], &self.values[idx]))
|
|
104
|
+
}
|
|
105
|
+
if idx == 0 && self.timestamps[idx] != ts{
|
|
106
|
+
return None
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (idx == self.__len__()) || self.timestamps[idx] != ts {
|
|
110
|
+
Some((&self.timestamps[idx-1], &self.values[idx-1]))
|
|
111
|
+
} else if self.timestamps[idx] == ts{
|
|
112
|
+
Some((&self.timestamps[idx], &self.values[idx]))
|
|
113
|
+
} else {
|
|
114
|
+
None
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fn point_on(&self, ts: i32) -> Option<(&i32, &Py<PyAny>)> {
|
|
119
|
+
if self.is_empty() {
|
|
120
|
+
return None
|
|
121
|
+
}
|
|
122
|
+
let idx = self.get_insertion_index(ts);
|
|
123
|
+
if (idx != self.__len__()) && self.timestamps[idx] == ts {
|
|
124
|
+
Some((&self.timestamps[idx], &self.values[idx]))
|
|
125
|
+
} else {
|
|
126
|
+
None
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fn points_between(&self, start_ts: i32, end_ts: i32) -> Vec<(&i32,&Py<PyAny>)> {
|
|
131
|
+
let start_idx = self.get_insertion_index(start_ts);
|
|
132
|
+
let end_idx = self.get_insertion_index(end_ts);
|
|
133
|
+
let mut return_vec = Vec::new();
|
|
134
|
+
for idx in start_idx..end_idx {
|
|
135
|
+
let point_ts = &self.timestamps[idx];
|
|
136
|
+
let value_ts = &self.values[idx];
|
|
137
|
+
return_vec.push((point_ts, value_ts))
|
|
138
|
+
}
|
|
139
|
+
return_vec
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fn as_dict(&self) -> HashMap<&i32, &Py<PyAny>> {
|
|
143
|
+
let mut return_dict = HashMap::new();
|
|
144
|
+
for idx in 0..self.__len__() {
|
|
145
|
+
let point_ts = &self.timestamps[idx];
|
|
146
|
+
let value_ts = &self.values[idx];
|
|
147
|
+
return_dict.insert(point_ts, value_ts);
|
|
148
|
+
}
|
|
149
|
+
return_dict
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fn as_list(&self) -> Vec<(&i32, &Py<PyAny>)> {
|
|
153
|
+
let mut return_list = Vec::new();
|
|
154
|
+
for idx in 0..self.__len__() {
|
|
155
|
+
let point_ts = &self.timestamps[idx];
|
|
156
|
+
let value_ts = &self.values[idx];
|
|
157
|
+
return_list.push((point_ts, value_ts));
|
|
158
|
+
}
|
|
159
|
+
return_list
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
#[pymodule]
|
|
165
|
+
fn generic_time_series_objects(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
166
|
+
m.add_class::<TimeSeriesObject>()?;
|
|
167
|
+
Ok(())
|
|
168
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 2
|
|
3
|
+
requires-python = ">=3.13"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "colorama"
|
|
7
|
+
version = "0.4.6"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
10
|
+
wheels = [
|
|
11
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "generic-time-series-objects"
|
|
16
|
+
version = "0.1.1"
|
|
17
|
+
source = { editable = "." }
|
|
18
|
+
dependencies = [
|
|
19
|
+
{ name = "pytest" },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[package.metadata]
|
|
23
|
+
requires-dist = [{ name = "pytest", specifier = ">=8.4.2" }]
|
|
24
|
+
|
|
25
|
+
[[package]]
|
|
26
|
+
name = "iniconfig"
|
|
27
|
+
version = "2.3.0"
|
|
28
|
+
source = { registry = "https://pypi.org/simple" }
|
|
29
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
|
30
|
+
wheels = [
|
|
31
|
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[[package]]
|
|
35
|
+
name = "packaging"
|
|
36
|
+
version = "25.0"
|
|
37
|
+
source = { registry = "https://pypi.org/simple" }
|
|
38
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
|
39
|
+
wheels = [
|
|
40
|
+
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[[package]]
|
|
44
|
+
name = "pluggy"
|
|
45
|
+
version = "1.6.0"
|
|
46
|
+
source = { registry = "https://pypi.org/simple" }
|
|
47
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
|
48
|
+
wheels = [
|
|
49
|
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[[package]]
|
|
53
|
+
name = "pygments"
|
|
54
|
+
version = "2.19.2"
|
|
55
|
+
source = { registry = "https://pypi.org/simple" }
|
|
56
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
|
57
|
+
wheels = [
|
|
58
|
+
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[[package]]
|
|
62
|
+
name = "pytest"
|
|
63
|
+
version = "8.4.2"
|
|
64
|
+
source = { registry = "https://pypi.org/simple" }
|
|
65
|
+
dependencies = [
|
|
66
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
67
|
+
{ name = "iniconfig" },
|
|
68
|
+
{ name = "packaging" },
|
|
69
|
+
{ name = "pluggy" },
|
|
70
|
+
{ name = "pygments" },
|
|
71
|
+
]
|
|
72
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
|
73
|
+
wheels = [
|
|
74
|
+
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
|
75
|
+
]
|