timelined_array 0.0.13__tar.gz → 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.
- {timelined_array-0.0.13 → timelined_array-0.1.0}/PKG-INFO +7 -3
- {timelined_array-0.0.13 → timelined_array-0.1.0}/README.md +4 -0
- timelined_array-0.1.0/pyproject.toml +84 -0
- {timelined_array-0.0.13 → timelined_array-0.1.0}/src/timelined_array/__init__.py +1 -1
- {timelined_array-0.0.13 → timelined_array-0.1.0}/src/timelined_array/time.py +94 -77
- {timelined_array-0.0.13 → timelined_array-0.1.0}/tests/test_timelined_array.py +59 -57
- timelined_array-0.0.13/pyproject.toml +0 -45
- {timelined_array-0.0.13 → timelined_array-0.1.0}/LICENSE +0 -0
- {timelined_array-0.0.13 → timelined_array-0.1.0}/tests/__init__.py +0 -0
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: timelined_array
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: Manage easily 1 or multidimensionnal samples numpy arrays that are time related. Extends numpy without removing any of it's abilities on such arrays.
|
|
5
|
-
Author-Email: Timothe Jost <
|
|
5
|
+
Author-Email: Timothe Jost <44769559+JostTim@users.noreply.github.com>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://pypi.org/project/timelined_array/
|
|
8
8
|
Project-URL: Source, https://github.com/JostTim/timelined_array
|
|
9
9
|
Requires-Python: >=3.11
|
|
10
|
-
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: numpy>=2
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
|
|
13
13
|
# timelined_array
|
|
14
14
|
|
|
15
|
+

|
|
16
|
+
[](https://badge.fury.io/py/timelined_array)
|
|
15
17
|

|
|
16
18
|

|
|
17
19
|
[](https://codecov.io/gh/JostTim/timelined_array)
|
|
18
20
|
|
|
21
|
+
<!-- eventually, add ?cache-bust=1 to badges url above, and update the value of cache-bust randomly at git commits (or based on the commit name) so that we always see the latest badge produced, and not the cached badge in the browser.-->
|
|
22
|
+
|
|
19
23
|
## Overview
|
|
20
24
|
The TimelinedArray package provides a set of classes and utilities for working with time-indexed arrays. It extends the functionality of NumPy arrays to include time-based indexing and operations, making it easier to work with time-series data.
|
|
21
25
|
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# timelined_array
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
[](https://badge.fury.io/py/timelined_array)
|
|
3
5
|

|
|
4
6
|

|
|
5
7
|
[](https://codecov.io/gh/JostTim/timelined_array)
|
|
6
8
|
|
|
9
|
+
<!-- eventually, add ?cache-bust=1 to badges url above, and update the value of cache-bust randomly at git commits (or based on the commit name) so that we always see the latest badge produced, and not the cached badge in the browser.-->
|
|
10
|
+
|
|
7
11
|
## Overview
|
|
8
12
|
The TimelinedArray package provides a set of classes and utilities for working with time-indexed arrays. It extends the functionality of NumPy arrays to include time-based indexing and operations, making it easier to work with time-series data.
|
|
9
13
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "timelined_array"
|
|
3
|
+
description = "Manage easily 1 or multidimensionnal samples numpy arrays that are time related. Extends numpy without removing any of it's abilities on such arrays."
|
|
4
|
+
authors = [
|
|
5
|
+
{ name = "Timothe Jost", email = "44769559+JostTim@users.noreply.github.com" },
|
|
6
|
+
]
|
|
7
|
+
dependencies = [
|
|
8
|
+
"numpy>=2",
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
dynamic = []
|
|
13
|
+
version = "0.1.0"
|
|
14
|
+
|
|
15
|
+
[project.license]
|
|
16
|
+
text = "MIT"
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://pypi.org/project/timelined_array/"
|
|
20
|
+
Source = "https://github.com/JostTim/timelined_array"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = [
|
|
24
|
+
"pdm-backend",
|
|
25
|
+
]
|
|
26
|
+
build-backend = "pdm.backend"
|
|
27
|
+
|
|
28
|
+
[tool.pdm]
|
|
29
|
+
distribution = true
|
|
30
|
+
|
|
31
|
+
[tool.pdm.version]
|
|
32
|
+
source = "file"
|
|
33
|
+
path = "src/timelined_array/__init__.py"
|
|
34
|
+
|
|
35
|
+
[tool.pdm.dev-dependencies]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=8.3.2",
|
|
38
|
+
"pytest-cov>=5.0.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
addopts = "--pdbcls=IPython.terminal.debugger:TerminalPdb --cov-report xml --cov --junitxml=pytest_results.xml"
|
|
43
|
+
testpaths = [
|
|
44
|
+
"tests",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.black]
|
|
48
|
+
preview = true
|
|
49
|
+
line-length = 120
|
|
50
|
+
|
|
51
|
+
[tool.flake8]
|
|
52
|
+
max-line-length = 120
|
|
53
|
+
ignore = [
|
|
54
|
+
"F401",
|
|
55
|
+
"F403",
|
|
56
|
+
"F841",
|
|
57
|
+
"E401",
|
|
58
|
+
"E265",
|
|
59
|
+
"E704",
|
|
60
|
+
"E266",
|
|
61
|
+
"E203",
|
|
62
|
+
"E712",
|
|
63
|
+
"W503",
|
|
64
|
+
"W605",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[tool.pyright]
|
|
68
|
+
reportGeneralTypeIssues = "information"
|
|
69
|
+
reportOptionalMemberAccess = "information"
|
|
70
|
+
reportMissingImports = "information"
|
|
71
|
+
reportMissingModuleSource = "information"
|
|
72
|
+
reportInvalidStringEscapeSequence = "information"
|
|
73
|
+
reportOptionalOperand = "none"
|
|
74
|
+
reportOptionalSubscript = "none"
|
|
75
|
+
reportOptionalIterable = "none"
|
|
76
|
+
reportAttributeAccessIssue = "information"
|
|
77
|
+
typeCheckingMode = "basic"
|
|
78
|
+
include = [
|
|
79
|
+
"src",
|
|
80
|
+
"tests",
|
|
81
|
+
]
|
|
82
|
+
exclude = [
|
|
83
|
+
"**/__pycache__",
|
|
84
|
+
]
|
|
@@ -5,7 +5,9 @@ from logging import getLogger
|
|
|
5
5
|
from enum import Enum
|
|
6
6
|
import operator
|
|
7
7
|
|
|
8
|
-
from typing import
|
|
8
|
+
from numpy.typing import NDArray
|
|
9
|
+
from types import MethodType
|
|
10
|
+
from typing import Tuple, List, Protocol, Type, Callable, Any, Optional
|
|
9
11
|
|
|
10
12
|
OperatorType = Callable[[Any, Any], bool]
|
|
11
13
|
|
|
@@ -177,7 +179,7 @@ class TimeIndexer:
|
|
|
177
179
|
_start_operation: OperatorType
|
|
178
180
|
_stop_operation: OperatorType
|
|
179
181
|
|
|
180
|
-
def __init__(self, array:
|
|
182
|
+
def __init__(self, array: "BaseTimeArray", start="inclusive", stop="exclusive"):
|
|
181
183
|
self.array = array
|
|
182
184
|
self.set_edge_policy(start, stop)
|
|
183
185
|
|
|
@@ -260,8 +262,7 @@ class TimeIndexer:
|
|
|
260
262
|
iindex_time = self.time_to_index(index)
|
|
261
263
|
full_iindex = self._insert_time_index(iindex_time)
|
|
262
264
|
# print("new full index : ",iindex_time)
|
|
263
|
-
logger.debug(
|
|
264
|
-
f"About to index over time with iindex_time {iindex_time} and full_iindex {full_iindex}")
|
|
265
|
+
logger.debug(f"About to index over time with iindex_time {iindex_time} and full_iindex {full_iindex}")
|
|
265
266
|
return self.array[full_iindex]
|
|
266
267
|
|
|
267
268
|
def get_iindex(self, sec_start=None, sec_stop=None, sec_step=None):
|
|
@@ -331,6 +332,11 @@ class TimeMixin:
|
|
|
331
332
|
start_policy = "inclusive"
|
|
332
333
|
stop_policy = "exclusive"
|
|
333
334
|
|
|
335
|
+
# ndarray inherited
|
|
336
|
+
ndim: int
|
|
337
|
+
shape: Tuple[int, ...]
|
|
338
|
+
__sub__: Callable
|
|
339
|
+
|
|
334
340
|
def _time_dimension_in_axis(self, axis: int | Tuple[int, ...] | None) -> bool:
|
|
335
341
|
"""Check if the time dimension is present in the specified axis.
|
|
336
342
|
|
|
@@ -371,8 +377,7 @@ class TimeMixin:
|
|
|
371
377
|
if axis < self.time_dimension:
|
|
372
378
|
final_time_dimension -= 1
|
|
373
379
|
elif axis == self.time_dimension:
|
|
374
|
-
raise ValueError(
|
|
375
|
-
"The time dimension would simply be discarded after axis removal")
|
|
380
|
+
raise ValueError("The time dimension would simply be discarded after axis removal")
|
|
376
381
|
|
|
377
382
|
return final_time_dimension
|
|
378
383
|
|
|
@@ -482,8 +487,7 @@ class TimeMixin:
|
|
|
482
487
|
# we apply this reshaping to timeline too.
|
|
483
488
|
|
|
484
489
|
final_timeline = (
|
|
485
|
-
self.timeline[index[time_dimension_in_index]] if len(
|
|
486
|
-
index) > time_dimension_in_index else self.timeline
|
|
490
|
+
self.timeline[index[time_dimension_in_index]] if len(index) > time_dimension_in_index else self.timeline
|
|
487
491
|
)
|
|
488
492
|
|
|
489
493
|
return index, final_timeline, final_time_dimension
|
|
@@ -505,7 +509,7 @@ class TimeMixin:
|
|
|
505
509
|
return self._get_slice_indexed_times(index)
|
|
506
510
|
|
|
507
511
|
@staticmethod
|
|
508
|
-
def _is_single_element(obj):
|
|
512
|
+
def _is_single_element(obj: np.ndarray):
|
|
509
513
|
"""Check if the input object is a single element.
|
|
510
514
|
|
|
511
515
|
Args:
|
|
@@ -519,27 +523,29 @@ class TimeMixin:
|
|
|
519
523
|
|
|
520
524
|
def _finish_axis_removing_operation(
|
|
521
525
|
self, result: "TimelinedArray| MaskedTimelinedArray | np.ndarray | int | float", axis: int | Tuple[int, ...]
|
|
522
|
-
) -> "TimelinedArray| MaskedTimelinedArray | np.ndarray | int | float":
|
|
526
|
+
) -> "TimelinedArray| MaskedTimelinedArray | np.ndarray | int | float | str":
|
|
523
527
|
"""Finish axis removing operation.
|
|
524
528
|
|
|
525
529
|
Args:
|
|
526
|
-
result (
|
|
530
|
+
result (BaseTimeArray): The result of the operation.
|
|
527
531
|
axis (int | Tuple[int, ...] | None): The axis or axes to remove.
|
|
528
532
|
|
|
529
533
|
Returns:
|
|
530
|
-
|
|
534
|
+
BaseTimeArray: The result after finishing the axis removing operation.
|
|
531
535
|
"""
|
|
532
536
|
|
|
533
|
-
if not isinstance(result, np.ndarray):
|
|
537
|
+
if not isinstance(result, np.ndarray) or not isinstance(result, TimeMixin):
|
|
534
538
|
return result
|
|
535
539
|
if self._is_single_element(result):
|
|
536
540
|
return result.item()
|
|
537
541
|
if self._time_dimension_in_axis(axis):
|
|
538
542
|
return np.asarray(result)
|
|
539
|
-
result.time_dimension = self._get_time_dimension_after_axis_removal(
|
|
540
|
-
axis)
|
|
543
|
+
result.time_dimension = self._get_time_dimension_after_axis_removal(axis)
|
|
541
544
|
return result
|
|
542
545
|
|
|
546
|
+
|
|
547
|
+
class BaseTimeArray(TimeMixin, np.ndarray):
|
|
548
|
+
|
|
543
549
|
# # REDUCE and SETSTATE are used to instanciate the array from and to a pickled serialized object.
|
|
544
550
|
# # We only need to store and retrieve time_dimension and timeline on top of the array's data
|
|
545
551
|
def __reduce__(self):
|
|
@@ -547,7 +553,7 @@ class TimeMixin:
|
|
|
547
553
|
object with additional attributes 'timeline' and 'time_dimension'."""
|
|
548
554
|
|
|
549
555
|
# Get the parent's __reduce__ tuple
|
|
550
|
-
pickled_state = super().__reduce__()
|
|
556
|
+
pickled_state: Tuple[Any, Any, tuple] = super().__reduce__() # type: ignore
|
|
551
557
|
# Create our own tuple to pass to __setstate__
|
|
552
558
|
# type: ignore
|
|
553
559
|
new_state = pickled_state[2] + (self.timeline, self.time_dimension)
|
|
@@ -556,11 +562,11 @@ class TimeMixin:
|
|
|
556
562
|
# Return a tuple that replaces the parent's __setstate__ tuple with our own
|
|
557
563
|
return (pickled_state[0], pickled_state[1], new_state)
|
|
558
564
|
|
|
559
|
-
def __setstate__(self:
|
|
565
|
+
def __setstate__(self: "BaseTimeArray", state):
|
|
560
566
|
"""Set the state of the object using the provided state tuple.
|
|
561
567
|
|
|
562
568
|
Args:
|
|
563
|
-
self (
|
|
569
|
+
self (BaseTimeArray): The BaseTimeArray object.
|
|
564
570
|
state: The state tuple containing information to set the object's attributes.
|
|
565
571
|
|
|
566
572
|
Returns:
|
|
@@ -582,11 +588,11 @@ class TimeMixin:
|
|
|
582
588
|
|
|
583
589
|
return hash((self.__array__(), self.timeline)) # type: ignore
|
|
584
590
|
|
|
585
|
-
def _get_array_cls(self) ->
|
|
591
|
+
def _get_array_cls(self) -> "BaseTimeArray":
|
|
586
592
|
"""Return the class of the array that is compatible with time operations.
|
|
587
593
|
|
|
588
594
|
Returns:
|
|
589
|
-
|
|
595
|
+
BaseTimeArray: The class of the array that is compatible with time operations.
|
|
590
596
|
"""
|
|
591
597
|
|
|
592
598
|
valid_types = [TimelinedArray, TimelinedArray]
|
|
@@ -596,7 +602,7 @@ class TimeMixin:
|
|
|
596
602
|
return np.ndarray # type: ignore
|
|
597
603
|
|
|
598
604
|
@property
|
|
599
|
-
def array_info(self:
|
|
605
|
+
def array_info(self: "BaseTimeArray"):
|
|
600
606
|
"""Return information about the array.
|
|
601
607
|
|
|
602
608
|
Returns:
|
|
@@ -609,11 +615,12 @@ class TimeMixin:
|
|
|
609
615
|
)
|
|
610
616
|
|
|
611
617
|
@property
|
|
612
|
-
def itime(self
|
|
613
|
-
"""Return a TimeIndexer object based on the given
|
|
618
|
+
def itime(self):
|
|
619
|
+
"""Return a TimeIndexer object based on the given BaseTimeArray object."""
|
|
614
620
|
|
|
615
621
|
return TimeIndexer(self, self.start_policy, self.stop_policy)
|
|
616
622
|
|
|
623
|
+
# backward compatibility
|
|
617
624
|
isec = itime
|
|
618
625
|
|
|
619
626
|
def align_trace(self, start: float, element_nb: int):
|
|
@@ -661,16 +668,21 @@ class TimeMixin:
|
|
|
661
668
|
indexer.append(slice(None))
|
|
662
669
|
indexer = tuple(indexer)
|
|
663
670
|
|
|
664
|
-
shift_area = self.itime.__getitem__(
|
|
665
|
-
period) if time_period else self.__getitem__(period)
|
|
671
|
+
shift_area = self.itime.__getitem__(period) if time_period else self.__getitem__(period)
|
|
666
672
|
|
|
667
673
|
# if not this : we lost a dimension because we sliced one axis to a single element, no need to no .mean
|
|
668
674
|
if not len(shift_area.shape) < len(self.shape):
|
|
669
675
|
shift_area = shift_area.mean(axis=axis)
|
|
670
676
|
|
|
677
|
+
if not isinstance(shift_area, np.ndarray):
|
|
678
|
+
raise NotImplementedError(
|
|
679
|
+
"Returning shift_values() of the array when shift area is a scalar value (due to .mean()) "
|
|
680
|
+
"is not yet implemented"
|
|
681
|
+
)
|
|
682
|
+
|
|
671
683
|
return self - np.repeat(shift_area.__getitem__(tuple(indexer)), self.shape[axis], axis=axis)
|
|
672
684
|
|
|
673
|
-
def swapaxes(self:
|
|
685
|
+
def swapaxes(self: "BaseTimeArray", axis1: int, axis2: int):
|
|
674
686
|
"""Swap the two specified axes of the TimelinedArray.
|
|
675
687
|
|
|
676
688
|
Args:
|
|
@@ -678,14 +690,13 @@ class TimeMixin:
|
|
|
678
690
|
axis2 (int): The second axis to be swapped.
|
|
679
691
|
|
|
680
692
|
Returns:
|
|
681
|
-
|
|
693
|
+
BaseTimeArray: A new TimelinedArray with the specified axes swapped.
|
|
682
694
|
"""
|
|
683
695
|
|
|
684
696
|
# we re-instanciate a TimelinedArray with view instead of the full constructor : faster
|
|
685
697
|
cls = self._get_array_cls()
|
|
686
698
|
|
|
687
|
-
swapped_array:
|
|
688
|
-
np.asarray(self), axis1, axis2).view(cls) # type: ignore
|
|
699
|
+
swapped_array: BaseTimeArray = np.swapaxes(np.asarray(self), axis1, axis2).view(cls) # type: ignore
|
|
689
700
|
swapped_array.timeline = self.timeline
|
|
690
701
|
|
|
691
702
|
if axis1 == self.time_dimension:
|
|
@@ -698,14 +709,14 @@ class TimeMixin:
|
|
|
698
709
|
# TimelinedArray.time_dimension and TimelinedArray.timeline are set. good to go
|
|
699
710
|
return swapped_array
|
|
700
711
|
|
|
701
|
-
def transpose(self
|
|
712
|
+
def transpose(self, *axes):
|
|
702
713
|
"""Transpose the array along the specified axes.
|
|
703
714
|
|
|
704
715
|
Args:
|
|
705
716
|
*axes: The axes to transpose the array along. If not provided, transposes the array in reverse order.
|
|
706
717
|
|
|
707
718
|
Returns:
|
|
708
|
-
|
|
719
|
+
BaseTimeArray: The transposed array with updated timeline and time dimension.
|
|
709
720
|
"""
|
|
710
721
|
|
|
711
722
|
if not axes:
|
|
@@ -714,8 +725,7 @@ class TimeMixin:
|
|
|
714
725
|
cls = self._get_array_cls()
|
|
715
726
|
|
|
716
727
|
# we re-instanciate a TimelinedArray with view instead of the full constructor : faster
|
|
717
|
-
transposed_array:
|
|
718
|
-
np.asarray(self), axes).view(cls) # type: ignore
|
|
728
|
+
transposed_array: BaseTimeArray = np.transpose(np.asarray(self), axes).view(cls) # type: ignore
|
|
719
729
|
transposed_array.timeline = self.timeline
|
|
720
730
|
|
|
721
731
|
if self.time_dimension in axes:
|
|
@@ -727,12 +737,12 @@ class TimeMixin:
|
|
|
727
737
|
return transposed_array
|
|
728
738
|
|
|
729
739
|
@property
|
|
730
|
-
def T(self
|
|
740
|
+
def T(self):
|
|
731
741
|
"""Transposes the object using the transpose method."""
|
|
732
742
|
|
|
733
743
|
return self.transpose()
|
|
734
744
|
|
|
735
|
-
def moveaxis(self:
|
|
745
|
+
def moveaxis(self: "BaseTimeArray", source: int | Tuple[int, ...], destination: int | Tuple[int, ...]):
|
|
736
746
|
"""Move the axis of the array to new positions.
|
|
737
747
|
|
|
738
748
|
Args:
|
|
@@ -740,7 +750,7 @@ class TimeMixin:
|
|
|
740
750
|
destination (int or Tuple[int, ...]): The destination position(s) to move the axis to.
|
|
741
751
|
|
|
742
752
|
Returns:
|
|
743
|
-
|
|
753
|
+
BaseTimeArray: A new array with the axis moved to the specified destination.
|
|
744
754
|
|
|
745
755
|
Note:
|
|
746
756
|
This method re-instantiates a TimelinedArray with a view instead of the full
|
|
@@ -755,9 +765,7 @@ class TimeMixin:
|
|
|
755
765
|
cls = self._get_array_cls()
|
|
756
766
|
|
|
757
767
|
# we re-instanciate a TimelinedArray with view instead of the full constructor : faster
|
|
758
|
-
moved_array:
|
|
759
|
-
cls
|
|
760
|
-
) # type: ignore
|
|
768
|
+
moved_array: BaseTimeArray = np.moveaxis(np.asarray(self), source, destination).view(cls) # type: ignore
|
|
761
769
|
moved_array.timeline = self.timeline
|
|
762
770
|
moved_array.time_dimension = self.time_dimension
|
|
763
771
|
|
|
@@ -774,7 +782,7 @@ class TimeMixin:
|
|
|
774
782
|
# TimelinedArray.time_dimension and TimelinedArray.timeline are set. good to go
|
|
775
783
|
return moved_array
|
|
776
784
|
|
|
777
|
-
def rollaxis(self:
|
|
785
|
+
def rollaxis(self: "BaseTimeArray", axis: int, start: int = 0):
|
|
778
786
|
"""Roll the axis of the TimelinedArray.
|
|
779
787
|
|
|
780
788
|
Args:
|
|
@@ -782,14 +790,13 @@ class TimeMixin:
|
|
|
782
790
|
start (int, optional): The position where the axis is placed. Defaults to 0.
|
|
783
791
|
|
|
784
792
|
Returns:
|
|
785
|
-
|
|
793
|
+
BaseTimeArray: A TimelinedArray with the rolled axis.
|
|
786
794
|
"""
|
|
787
795
|
|
|
788
796
|
# we re-instanciate a TimelinedArray with view instead of the full constructor : faster
|
|
789
797
|
cls = self._get_array_cls()
|
|
790
798
|
|
|
791
|
-
rolled_array:
|
|
792
|
-
np.asarray(self), axis, start).view(cls) # type: ignore
|
|
799
|
+
rolled_array: BaseTimeArray = np.rollaxis(np.asarray(self), axis, start).view(cls) # type: ignore
|
|
793
800
|
|
|
794
801
|
# reinject timeline as is
|
|
795
802
|
rolled_array.timeline = self.timeline
|
|
@@ -815,14 +822,14 @@ class TimeMixin:
|
|
|
815
822
|
mapping = {i: new_order.index(i) for i in range(n)}
|
|
816
823
|
return mapping
|
|
817
824
|
|
|
818
|
-
rolled_array.time_dimension = rollaxis_mapping(
|
|
819
|
-
self.shape, axis, start)[self.time_dimension]
|
|
825
|
+
rolled_array.time_dimension = rollaxis_mapping(self.shape, axis, start)[self.time_dimension]
|
|
820
826
|
|
|
821
827
|
return rolled_array
|
|
822
828
|
|
|
823
|
-
def
|
|
824
|
-
|
|
825
|
-
|
|
829
|
+
def all_axes(self):
|
|
830
|
+
return tuple([i for i in range(self.ndim)])
|
|
831
|
+
|
|
832
|
+
def mean(self, axis: int | Tuple[int, ...] | None = None, dtype=None, out=None, keepdims=False):
|
|
826
833
|
"""Calculates the mean along the specified axis.
|
|
827
834
|
|
|
828
835
|
Args:
|
|
@@ -835,14 +842,13 @@ class TimeMixin:
|
|
|
835
842
|
Returns:
|
|
836
843
|
ndarray: Mean of the input array along the specified axis.
|
|
837
844
|
"""
|
|
838
|
-
|
|
845
|
+
if axis is None:
|
|
846
|
+
axis = self.all_axes()
|
|
839
847
|
result = super().mean(axis=axis, dtype=dtype, out=out, keepdims=keepdims)
|
|
840
848
|
return self._finish_axis_removing_operation(result, axis)
|
|
841
849
|
|
|
842
850
|
# Override other reduction methods similarly if needed
|
|
843
|
-
def sum(
|
|
844
|
-
self: TimeCompatibleProtocol, axis: int | Tuple[int, ...] | None = None, dtype=None, out=None, keepdims=False
|
|
845
|
-
):
|
|
851
|
+
def sum(self, axis: int | Tuple[int, ...] | None = None, dtype=None, out=None, keepdims=False):
|
|
846
852
|
"""Calculate the sum along the specified axis.
|
|
847
853
|
|
|
848
854
|
Args:
|
|
@@ -861,12 +867,13 @@ class TimeMixin:
|
|
|
861
867
|
Returns:
|
|
862
868
|
The sum of the input array along the specified axis.
|
|
863
869
|
"""
|
|
864
|
-
|
|
870
|
+
if axis is None:
|
|
871
|
+
axis = self.all_axes()
|
|
865
872
|
result = super().sum(axis=axis, dtype=dtype, out=out, keepdims=keepdims)
|
|
866
873
|
return self._finish_axis_removing_operation(result, axis)
|
|
867
874
|
|
|
868
875
|
def std(
|
|
869
|
-
self:
|
|
876
|
+
self: "BaseTimeArray",
|
|
870
877
|
axis: int | Tuple[int, ...] | None = None,
|
|
871
878
|
dtype=None,
|
|
872
879
|
out=None,
|
|
@@ -889,12 +896,13 @@ class TimeMixin:
|
|
|
889
896
|
ndarray: A new array containing the standard deviation
|
|
890
897
|
of elements along the specified axis after removing the axis.
|
|
891
898
|
"""
|
|
892
|
-
|
|
899
|
+
if axis is None:
|
|
900
|
+
axis = self.all_axes()
|
|
893
901
|
result = super().std(axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims)
|
|
894
902
|
return self._finish_axis_removing_operation(result, axis)
|
|
895
903
|
|
|
896
904
|
def var(
|
|
897
|
-
self:
|
|
905
|
+
self: "BaseTimeArray",
|
|
898
906
|
axis: int | Tuple[int, ...] | None = None,
|
|
899
907
|
dtype=None,
|
|
900
908
|
out=None,
|
|
@@ -904,7 +912,7 @@ class TimeMixin:
|
|
|
904
912
|
"""Calculate the variance along the specified axis.
|
|
905
913
|
|
|
906
914
|
Args:
|
|
907
|
-
self (
|
|
915
|
+
self (BaseTimeArray): The input data.
|
|
908
916
|
axis (int | Tuple[int, ...] | None): Axis or axes along which the variance is computed.
|
|
909
917
|
The default is to compute the variance of the flattened array.
|
|
910
918
|
dtype: Data-type of the result. If not provided, the data-type of the input is used.
|
|
@@ -918,7 +926,8 @@ class TimeMixin:
|
|
|
918
926
|
Returns:
|
|
919
927
|
ndarray: A new array containing the variance of the input array along the specified axis.
|
|
920
928
|
"""
|
|
921
|
-
|
|
929
|
+
if axis is None:
|
|
930
|
+
axis = self.all_axes()
|
|
922
931
|
result = super().var(axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims)
|
|
923
932
|
return self._finish_axis_removing_operation(result, axis)
|
|
924
933
|
|
|
@@ -936,7 +945,7 @@ class TimeMixin:
|
|
|
936
945
|
# and shift the rest accordingly
|
|
937
946
|
cls = self._get_array_cls()
|
|
938
947
|
# type: ignore
|
|
939
|
-
return cls(self, timeline=self.timeline - self.timeline[at])
|
|
948
|
+
return cls(self, timeline=self.timeline - self.timeline[at]) # type: ignore
|
|
940
949
|
|
|
941
950
|
def offset_timeline(self, offset):
|
|
942
951
|
"""Returns a modified version of the array with time offset.
|
|
@@ -1004,8 +1013,7 @@ class TimeMixin:
|
|
|
1004
1013
|
break
|
|
1005
1014
|
|
|
1006
1015
|
if timeline is None:
|
|
1007
|
-
raise ValueError(
|
|
1008
|
-
"timeline must be supplied if the input_array is not a TimelinedArray")
|
|
1016
|
+
raise ValueError("timeline must be supplied if the input_array is not a TimelinedArray")
|
|
1009
1017
|
|
|
1010
1018
|
if time_dimension is None: # same thing for the time dimension.
|
|
1011
1019
|
time_dimension = getattr(data, "time_dimension", None)
|
|
@@ -1053,7 +1061,7 @@ class TimePacker:
|
|
|
1053
1061
|
return iter((self.array.timeline, self.array.__array__()))
|
|
1054
1062
|
|
|
1055
1063
|
|
|
1056
|
-
class TimelinedArray(
|
|
1064
|
+
class TimelinedArray(BaseTimeArray):
|
|
1057
1065
|
"""
|
|
1058
1066
|
The TimelinedArray class is a subclass of the numpy.ndarray class, which represents a multi-dimensional
|
|
1059
1067
|
array of homogeneous data. This class adds additional functionality
|
|
@@ -1085,9 +1093,16 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
|
|
|
1085
1093
|
|
|
1086
1094
|
"""
|
|
1087
1095
|
|
|
1096
|
+
# backward compatibility
|
|
1088
1097
|
TA_Timeline = Timeline
|
|
1089
1098
|
|
|
1090
|
-
def __new__(
|
|
1099
|
+
def __new__(
|
|
1100
|
+
cls,
|
|
1101
|
+
data,
|
|
1102
|
+
timeline: Optional[Timeline | np.ndarray | list] = None,
|
|
1103
|
+
time_dimension: int | None = None,
|
|
1104
|
+
uniform_space=False,
|
|
1105
|
+
) -> "TimelinedArray":
|
|
1091
1106
|
"""Create a new TimelinedArray object from the input data.
|
|
1092
1107
|
|
|
1093
1108
|
Args:
|
|
@@ -1100,7 +1115,7 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
|
|
|
1100
1115
|
TimelinedArray: A new TimelinedArray object.
|
|
1101
1116
|
"""
|
|
1102
1117
|
|
|
1103
|
-
data, timeline, time_dimension =
|
|
1118
|
+
data, timeline, time_dimension = BaseTimeArray.extract_time_from_data(
|
|
1104
1119
|
data, timeline=timeline, time_dimension=time_dimension, uniform_space=uniform_space
|
|
1105
1120
|
)
|
|
1106
1121
|
|
|
@@ -1137,7 +1152,12 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
|
|
|
1137
1152
|
self.timeline = getattr(obj, "timeline", Timeline([]))
|
|
1138
1153
|
self.time_dimension = getattr(obj, "time_dimension", 0)
|
|
1139
1154
|
|
|
1140
|
-
def __array_wrap__(
|
|
1155
|
+
def __array_wrap__(
|
|
1156
|
+
self,
|
|
1157
|
+
out_arr,
|
|
1158
|
+
context=None,
|
|
1159
|
+
return_scalar=False,
|
|
1160
|
+
):
|
|
1141
1161
|
"""Wrap the output array after a ufunc operation.
|
|
1142
1162
|
|
|
1143
1163
|
Args:
|
|
@@ -1154,10 +1174,9 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
|
|
|
1154
1174
|
|
|
1155
1175
|
if context is not None:
|
|
1156
1176
|
logger.debug(f"wrapping array after ufunc {context[0].__name__}")
|
|
1157
|
-
output = super().__array_wrap__(out_arr, context)
|
|
1177
|
+
output = super().__array_wrap__(out_arr, context, return_scalar)
|
|
1158
1178
|
if len(output.shape) < len(self.shape):
|
|
1159
|
-
logger.debug(
|
|
1160
|
-
f"shape reduced from : {self.shape} to : {output.shape}. outarray was : {out_arr.shape}")
|
|
1179
|
+
logger.debug(f"shape reduced from : {self.shape} to : {output.shape}. outarray was : {out_arr.shape}")
|
|
1161
1180
|
return output
|
|
1162
1181
|
|
|
1163
1182
|
def __array_function__(self, func, types, args, kwargs):
|
|
@@ -1189,8 +1208,7 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
|
|
|
1189
1208
|
TimelinedArray | np.ndarray: Indexed result based on the provided index.
|
|
1190
1209
|
"""
|
|
1191
1210
|
|
|
1192
|
-
index, final_timeline, final_time_dimension = self._get_indexed_times(
|
|
1193
|
-
index)
|
|
1211
|
+
index, final_timeline, final_time_dimension = self._get_indexed_times(index)
|
|
1194
1212
|
|
|
1195
1213
|
if final_timeline is None or final_time_dimension is None:
|
|
1196
1214
|
return np.asarray(self).__getitem__(index)
|
|
@@ -1240,19 +1258,19 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
|
|
|
1240
1258
|
return TimelinedArray(aligned_arrays)
|
|
1241
1259
|
|
|
1242
1260
|
|
|
1243
|
-
class MaskedTimelinedArray(
|
|
1261
|
+
class MaskedTimelinedArray(np.ma.MaskedArray, BaseTimeArray):
|
|
1244
1262
|
def __new__(
|
|
1245
1263
|
cls,
|
|
1246
1264
|
data,
|
|
1247
|
-
mask=np.ma.nomask,
|
|
1265
|
+
mask: NDArray[np.bool_] | np.bool_ | bool | np.ma.MaskedArray = np.ma.nomask,
|
|
1248
1266
|
dtype=None,
|
|
1249
1267
|
copy=False,
|
|
1250
1268
|
fill_value=None,
|
|
1251
1269
|
keep_mask=True,
|
|
1252
1270
|
hard_mask=False,
|
|
1253
1271
|
shrink=True,
|
|
1254
|
-
timeline=None,
|
|
1255
|
-
time_dimension=None,
|
|
1272
|
+
timeline: Optional[Timeline | np.ndarray | list] | None = None,
|
|
1273
|
+
time_dimension: Optional[int] = None,
|
|
1256
1274
|
uniform_space=False,
|
|
1257
1275
|
**kwargs,
|
|
1258
1276
|
):
|
|
@@ -1277,7 +1295,7 @@ class MaskedTimelinedArray(TimeMixin, np.ma.MaskedArray, TimeCompatibleProtocol)
|
|
|
1277
1295
|
An instance of the class with the specified parameters.
|
|
1278
1296
|
"""
|
|
1279
1297
|
|
|
1280
|
-
_, timeline, time_dimension =
|
|
1298
|
+
_, timeline, time_dimension = BaseTimeArray.extract_time_from_data(
|
|
1281
1299
|
data, timeline=timeline, time_dimension=time_dimension, uniform_space=uniform_space
|
|
1282
1300
|
)
|
|
1283
1301
|
|
|
@@ -1326,8 +1344,7 @@ class MaskedTimelinedArray(TimeMixin, np.ma.MaskedArray, TimeCompatibleProtocol)
|
|
|
1326
1344
|
MaskedTimelinedArray | np.ma.MaskedArray: The masked array or MaskedTimelinedArray based on the index.
|
|
1327
1345
|
"""
|
|
1328
1346
|
|
|
1329
|
-
index, final_timeline, final_time_dimension = self._get_indexed_times(
|
|
1330
|
-
index)
|
|
1347
|
+
index, final_timeline, final_time_dimension = self._get_indexed_times(index)
|
|
1331
1348
|
|
|
1332
1349
|
if final_timeline is None or final_time_dimension is None:
|
|
1333
1350
|
return np.ma.MaskedArray(data=np.asarray(self), mask=self.mask, fill_value=self.fill_value).__getitem__(
|
|
@@ -1,52 +1,49 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import pytest
|
|
3
3
|
from timelined_array import TimelinedArray, MaskedTimelinedArray
|
|
4
|
-
from timelined_array.time import (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
from timelined_array.time import (
|
|
5
|
+
Timeline,
|
|
6
|
+
StartBoundary,
|
|
7
|
+
StoptBoundary,
|
|
8
|
+
EdgePolicy,
|
|
9
|
+
TimeIndexer,
|
|
10
|
+
TimeMixin,
|
|
11
|
+
TimePacker,
|
|
12
|
+
Seconds,
|
|
13
|
+
)
|
|
14
|
+
import pickle
|
|
15
|
+
from pathlib import Path
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
@pytest.fixture
|
|
15
19
|
def timelined_array_1D():
|
|
16
|
-
return TimelinedArray(np.random.rand(50),
|
|
17
|
-
timeline=np.arange(50),
|
|
18
|
-
time_dimension=0)
|
|
20
|
+
return TimelinedArray(np.random.rand(50), timeline=np.arange(50), time_dimension=0)
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
@pytest.fixture
|
|
22
24
|
def timelined_array_3D():
|
|
23
|
-
return TimelinedArray(np.random.rand(25, 50, 75),
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
return TimelinedArray(np.random.rand(25, 50, 75), timeline=np.arange(50), time_dimension=1)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def pickle_path():
|
|
30
|
+
file_path = Path("serialized_test_3D_ta_array.pickle")
|
|
31
|
+
yield file_path
|
|
32
|
+
if file_path.exists():
|
|
33
|
+
file_path.unlink(missing_ok=True)
|
|
26
34
|
|
|
27
35
|
|
|
28
36
|
def get_axis_parameters_3D():
|
|
29
37
|
# affected_axis, time_dimension, shape
|
|
30
38
|
# if None, we expect an AttributeError
|
|
31
|
-
return [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
((0, 2), 0, (50,)),
|
|
36
|
-
(None, None, None)
|
|
37
|
-
]
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@pytest.mark.parametrize("shape, timeline_dimension", [
|
|
41
|
-
((12,), 0),
|
|
42
|
-
((10, 30, 50), 2),
|
|
43
|
-
((29, 34, 61, 15, 4), 0)
|
|
44
|
-
])
|
|
39
|
+
return [(0, 0, (50, 75)), (1, None, (25, 75)), (2, 1, (25, 50)), ((0, 2), 0, (50,)), (None, None, None)]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.parametrize("shape, timeline_dimension", [((12,), 0), ((10, 30, 50), 2), ((29, 34, 61, 15, 4), 0)])
|
|
45
43
|
def test_ta_array_shape(shape, timeline_dimension):
|
|
46
|
-
ta_array = TimelinedArray(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
time_dimension=timeline_dimension)
|
|
44
|
+
ta_array = TimelinedArray(
|
|
45
|
+
np.random.rand(*shape), timeline=np.arange(shape[timeline_dimension]), time_dimension=timeline_dimension
|
|
46
|
+
)
|
|
50
47
|
|
|
51
48
|
assert ta_array.shape == shape
|
|
52
49
|
assert ta_array.timeline.shape == (shape[timeline_dimension],)
|
|
@@ -58,6 +55,25 @@ def test_timeline_creation():
|
|
|
58
55
|
assert timeline.max() == 9
|
|
59
56
|
assert timeline.min() == 0
|
|
60
57
|
|
|
58
|
+
with pytest.raises(NotImplementedError):
|
|
59
|
+
timeline = Timeline(np.random.rand(10), uniform_space=True)
|
|
60
|
+
# assert np.unique(np.diff(timeline)).size == 1
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_pickle_unpickle(timelined_array_3D, pickle_path):
|
|
64
|
+
# Pickle the numpy array
|
|
65
|
+
with open(pickle_path, "wb") as f:
|
|
66
|
+
pickle.dump(timelined_array_3D, f)
|
|
67
|
+
|
|
68
|
+
# Unpickle the numpy array
|
|
69
|
+
with open(pickle_path, "rb") as f:
|
|
70
|
+
unserialized_3D_array = pickle.load(f)
|
|
71
|
+
|
|
72
|
+
# Assert that the original and unpickled arrays are the same
|
|
73
|
+
np.testing.assert_array_equal(timelined_array_3D, unserialized_3D_array)
|
|
74
|
+
np.testing.assert_array_equal(timelined_array_3D.timeline, unserialized_3D_array.timeline)
|
|
75
|
+
assert timelined_array_3D.time_dimension == unserialized_3D_array.time_dimension
|
|
76
|
+
|
|
61
77
|
|
|
62
78
|
def test_start_boundary():
|
|
63
79
|
assert StartBoundary.inclusive.value(5, 5)
|
|
@@ -97,27 +113,18 @@ def test_ta_swapaxes(timelined_array_3D):
|
|
|
97
113
|
assert swapped.time_dimension == 2
|
|
98
114
|
|
|
99
115
|
|
|
100
|
-
@pytest.mark.parametrize(
|
|
101
|
-
|
|
102
|
-
(2, 1, 2, (25, 75, 50)),
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def test_ta_rollaxis(rolled_axis,
|
|
106
|
-
end_position,
|
|
107
|
-
expected_time_position,
|
|
108
|
-
expected_shape,
|
|
109
|
-
timelined_array_3D):
|
|
116
|
+
@pytest.mark.parametrize(
|
|
117
|
+
"rolled_axis, end_position, expected_time_position, expected_shape",
|
|
118
|
+
[(0, 2, 0, (50, 25, 75)), (2, 1, 2, (25, 75, 50)), (1, 3, 2, (25, 75, 50))],
|
|
119
|
+
)
|
|
120
|
+
def test_ta_rollaxis(rolled_axis, end_position, expected_time_position, expected_shape, timelined_array_3D):
|
|
110
121
|
# roll back the axis
|
|
111
122
|
rolled = timelined_array_3D.rollaxis(rolled_axis, end_position)
|
|
112
123
|
assert rolled.shape == expected_shape
|
|
113
124
|
assert rolled.time_dimension == expected_time_position
|
|
114
125
|
|
|
115
126
|
|
|
116
|
-
def axis_affecting_functions_blueprint(timelined_array_3D,
|
|
117
|
-
func,
|
|
118
|
-
affected_axis,
|
|
119
|
-
time_dimension,
|
|
120
|
-
shape):
|
|
127
|
+
def axis_affecting_functions_blueprint(timelined_array_3D, func, affected_axis, time_dimension, shape):
|
|
121
128
|
result = func(timelined_array_3D, axis=affected_axis)
|
|
122
129
|
if time_dimension is None:
|
|
123
130
|
with pytest.raises(AttributeError):
|
|
@@ -134,33 +141,28 @@ def axis_affecting_functions_blueprint(timelined_array_3D,
|
|
|
134
141
|
|
|
135
142
|
@pytest.mark.parametrize("affected_axis, time_dimension, shape", get_axis_parameters_3D())
|
|
136
143
|
def test_ta_mean(timelined_array_3D, affected_axis, time_dimension, shape):
|
|
137
|
-
axis_affecting_functions_blueprint(timelined_array_3D, np.mean,
|
|
138
|
-
affected_axis, time_dimension, shape)
|
|
144
|
+
axis_affecting_functions_blueprint(timelined_array_3D, np.mean, affected_axis, time_dimension, shape)
|
|
139
145
|
|
|
140
146
|
|
|
141
147
|
@pytest.mark.parametrize("affected_axis, time_dimension, shape", get_axis_parameters_3D())
|
|
142
148
|
def test_ta_sum(timelined_array_3D, affected_axis, time_dimension, shape):
|
|
143
|
-
axis_affecting_functions_blueprint(timelined_array_3D, np.sum,
|
|
144
|
-
affected_axis, time_dimension, shape)
|
|
149
|
+
axis_affecting_functions_blueprint(timelined_array_3D, np.sum, affected_axis, time_dimension, shape)
|
|
145
150
|
|
|
146
151
|
|
|
147
152
|
@pytest.mark.parametrize("affected_axis, time_dimension, shape", get_axis_parameters_3D())
|
|
148
153
|
def test_ta_std(timelined_array_3D, affected_axis, time_dimension, shape):
|
|
149
|
-
axis_affecting_functions_blueprint(timelined_array_3D, np.std,
|
|
150
|
-
affected_axis, time_dimension, shape)
|
|
154
|
+
axis_affecting_functions_blueprint(timelined_array_3D, np.std, affected_axis, time_dimension, shape)
|
|
151
155
|
|
|
152
156
|
|
|
153
157
|
@pytest.mark.parametrize("affected_axis, time_dimension, shape", get_axis_parameters_3D())
|
|
154
158
|
def test_ta_var(timelined_array_3D, affected_axis, time_dimension, shape):
|
|
155
|
-
axis_affecting_functions_blueprint(timelined_array_3D, np.var,
|
|
156
|
-
affected_axis, time_dimension, shape)
|
|
159
|
+
axis_affecting_functions_blueprint(timelined_array_3D, np.var, affected_axis, time_dimension, shape)
|
|
157
160
|
|
|
158
161
|
|
|
159
162
|
def test_masked_ta_creation():
|
|
160
163
|
data = np.random.rand(10, 10)
|
|
161
164
|
mask = data > 0.5
|
|
162
|
-
masked_ta = MaskedTimelinedArray(
|
|
163
|
-
data, mask=mask, timeline=np.arange(10), time_dimension=0)
|
|
165
|
+
masked_ta = MaskedTimelinedArray(data, mask=mask, timeline=np.arange(10), time_dimension=0)
|
|
164
166
|
assert isinstance(masked_ta, MaskedTimelinedArray)
|
|
165
167
|
assert masked_ta.shape == (10, 10)
|
|
166
168
|
assert masked_ta.time_dimension == 0
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "timelined_array"
|
|
3
|
-
description = "Manage easily 1 or multidimensionnal samples numpy arrays that are time related. Extends numpy without removing any of it's abilities on such arrays."
|
|
4
|
-
authors = [
|
|
5
|
-
{ name = "Timothe Jost", email = "timothe.jost@wanadoo.fr" },
|
|
6
|
-
]
|
|
7
|
-
dependencies = [
|
|
8
|
-
"numpy",
|
|
9
|
-
]
|
|
10
|
-
requires-python = ">=3.11"
|
|
11
|
-
readme = "README.md"
|
|
12
|
-
dynamic = []
|
|
13
|
-
version = "0.0.13"
|
|
14
|
-
|
|
15
|
-
[project.license]
|
|
16
|
-
text = "MIT"
|
|
17
|
-
|
|
18
|
-
[project.urls]
|
|
19
|
-
Homepage = "https://pypi.org/project/timelined_array/"
|
|
20
|
-
Source = "https://github.com/JostTim/timelined_array"
|
|
21
|
-
|
|
22
|
-
[build-system]
|
|
23
|
-
requires = [
|
|
24
|
-
"pdm-backend",
|
|
25
|
-
]
|
|
26
|
-
build-backend = "pdm.backend"
|
|
27
|
-
|
|
28
|
-
[tool.pdm]
|
|
29
|
-
distribution = true
|
|
30
|
-
|
|
31
|
-
[tool.pdm.version]
|
|
32
|
-
source = "file"
|
|
33
|
-
path = "src/timelined_array/__init__.py"
|
|
34
|
-
|
|
35
|
-
[tool.pdm.dev-dependencies]
|
|
36
|
-
dev = [
|
|
37
|
-
"pytest>=8.3.2",
|
|
38
|
-
"pytest-cov>=5.0.0",
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
[tool.pytest.ini_options]
|
|
42
|
-
addopts = "--pdbcls=IPython.terminal.debugger:TerminalPdb --cov-report html --cov --junitxml=pytest_results.xml"
|
|
43
|
-
testpaths = [
|
|
44
|
-
"tests",
|
|
45
|
-
]
|
|
File without changes
|
|
File without changes
|