tts-data-utils 0.7.3__py3-none-any.whl

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.
Files changed (72) hide show
  1. tts_data_utils/__init__.py +0 -0
  2. tts_data_utils/core/__init__.py +0 -0
  3. tts_data_utils/core/container_history.py +79 -0
  4. tts_data_utils/core/data_container.py +2102 -0
  5. tts_data_utils/core/data_item.py +487 -0
  6. tts_data_utils/core/diff.py +155 -0
  7. tts_data_utils/core/generic.py +78 -0
  8. tts_data_utils/core/lorem_utils.py +206 -0
  9. tts_data_utils/core/visual_diff.py +148 -0
  10. tts_data_utils/invulnerable_data_manager/__init__.py +0 -0
  11. tts_data_utils/invulnerable_data_manager/batch.py +176 -0
  12. tts_data_utils/invulnerable_data_manager/invulnerable_data_manager.py +171 -0
  13. tts_data_utils/invulnerable_data_manager/utilities.py +151 -0
  14. tts_data_utils/multimission/__init__.py +0 -0
  15. tts_data_utils/multimission/alarms.py +109 -0
  16. tts_data_utils/multimission/eha.py +253 -0
  17. tts_data_utils/multimission/evr.py +340 -0
  18. tts_data_utils/multimission/evr_gaps.py +148 -0
  19. tts_data_utils/multimission/expected_lad.py +161 -0
  20. tts_data_utils/multimission/planning_rule.py +106 -0
  21. tts_data_utils/multimission/taco.py +53 -0
  22. tts_data_utils/test/__init__.py +0 -0
  23. tts_data_utils/test/core/__init__.py +0 -0
  24. tts_data_utils/test/core/test_data_container.py +403 -0
  25. tts_data_utils/test/core/test_data_item.py +92 -0
  26. tts_data_utils/test/core/test_diff.py +173 -0
  27. tts_data_utils/test/core/test_files/diff/diff_dict/actual_diff.csv +5135 -0
  28. tts_data_utils/test/core/test_files/diff/diff_dict/expected_diff.csv +5135 -0
  29. tts_data_utils/test/core/test_files/diff/diff_dict/left.csv +637 -0
  30. tts_data_utils/test/core/test_files/diff/diff_dict/right.csv +637 -0
  31. tts_data_utils/test/core/test_files/diff/diff_list/actual_diff.csv +5135 -0
  32. tts_data_utils/test/core/test_files/diff/diff_list/expected_diff.csv +5135 -0
  33. tts_data_utils/test/core/test_files/diff/diff_list/left.csv +637 -0
  34. tts_data_utils/test/core/test_files/diff/diff_list/right.csv +637 -0
  35. tts_data_utils/test/core/test_files/diff/diff_list_size/actual_diff.csv +33 -0
  36. tts_data_utils/test/core/test_files/diff/diff_list_size/expected_diff.csv +33 -0
  37. tts_data_utils/test/core/test_files/diff/diff_list_size/left.csv +637 -0
  38. tts_data_utils/test/core/test_files/diff/diff_list_size/right.csv +637 -0
  39. tts_data_utils/test/core/test_files/diff/diff_simple_types/actual_diff.csv +17894 -0
  40. tts_data_utils/test/core/test_files/diff/diff_simple_types/expected_diff.csv +17894 -0
  41. tts_data_utils/test/core/test_files/diff/diff_simple_types/left.csv +637 -0
  42. tts_data_utils/test/core/test_files/diff/diff_simple_types/right.csv +637 -0
  43. tts_data_utils/test/core/test_files/diff/diff_type/actual_diff.csv +5135 -0
  44. tts_data_utils/test/core/test_files/diff/diff_type/expected_diff.csv +5135 -0
  45. tts_data_utils/test/core/test_files/diff/diff_type/left.csv +637 -0
  46. tts_data_utils/test/core/test_files/diff/diff_type/right.csv +637 -0
  47. tts_data_utils/test/core/test_files/diff/same_not_same/actual_diff.csv +5135 -0
  48. tts_data_utils/test/core/test_files/visdiff/adjacent_insert_delete.html +1581 -0
  49. tts_data_utils/test/core/test_files/visdiff/insert_and_delete.html +1581 -0
  50. tts_data_utils/test/core/test_files/visdiff/no_diff.html +1581 -0
  51. tts_data_utils/test/core/test_files/visdiff/overlapping_insert_delete.html +1541 -0
  52. tts_data_utils/test/core/test_files/visdiff/replace.html +1581 -0
  53. tts_data_utils/test/core/test_files/visdiff/replace_with_delete.html +1581 -0
  54. tts_data_utils/test/core/test_files/visdiff/simple_delete.html +1581 -0
  55. tts_data_utils/test/core/test_files/visdiff/simple_insert.html +1581 -0
  56. tts_data_utils/test/core/test_files/visdiff/stressing_case.html +2061 -0
  57. tts_data_utils/test/core/test_filters.py +319 -0
  58. tts_data_utils/test/core/test_generic_container.py +22 -0
  59. tts_data_utils/test/core/test_history.py +44 -0
  60. tts_data_utils/test/core/test_visdiff.py +193 -0
  61. tts_data_utils/test/invulnerable_data_manager/test_invulnerable_data_manager.py +150 -0
  62. tts_data_utils/test/multimission/__init__.py +0 -0
  63. tts_data_utils/test/multimission/evr/__init__.py +0 -0
  64. tts_data_utils/test/multimission/evr/test_evr.py +29 -0
  65. tts_data_utils/test/multimission/evr/test_files/actual_evr_gaps.csv +12 -0
  66. tts_data_utils/test/multimission/evr/test_files/evrs_with_gaps.csv +30 -0
  67. tts_data_utils/test/multimission/evr/test_files/expected_evr_gaps.csv +12 -0
  68. tts_data_utils/util.py +104 -0
  69. tts_data_utils-0.7.3.dist-info/METADATA +81 -0
  70. tts_data_utils-0.7.3.dist-info/RECORD +72 -0
  71. tts_data_utils-0.7.3.dist-info/WHEEL +5 -0
  72. tts_data_utils-0.7.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,106 @@
1
+ import pdb
2
+ from abc import ABC, abstractmethod
3
+
4
+ from tts_data_utils.core.data_container import DataContainer
5
+ from tts_data_utils.core.data_item import DataItem
6
+
7
+ from jpl_time import Time
8
+
9
+ class PlanningRuleItem(DataItem):
10
+ """
11
+ A specialized record representing a mission constraint or operational rule.
12
+
13
+ **The Concept:**
14
+ Planning rules are the "laws" of a spacecraft's schedule. They define what the
15
+ spacecraft can and cannot do (e.g., "Do not point the instrument at the sun
16
+ while the cover is open").
17
+
18
+ A `PlanningRuleItem` captures the metadata of these constraints, allowing
19
+ automated schedulers or human planners to understand the risk and impact
20
+ associated with violating a specific mission requirement.
21
+
22
+ **Database Integration:**
23
+ This class includes a `DJANGO_KEY_MAP` to handle the translation between
24
+ database-friendly snake_case fields (used in the Planning Service) and
25
+ report-friendly Title Case fields used in this library.
26
+
27
+ :param Rule ID: Unique identifier for the planning constraint.
28
+ :type Rule ID: str
29
+ :param Functional Category: The subsystem the rule applies to (e.g., 'ADCS', 'Power').
30
+ :type Functional Category: str
31
+ :param Maturity: The current status of the rule (e.g., 'Draft', 'Flight Validated').
32
+ :type Maturity: str
33
+ :param Title: A short, descriptive name for the rule.
34
+ :type Title: str
35
+ :param Description: The full text of the constraint.
36
+ :type Description: str
37
+ :param Violation Impact: Explanation of what happens if this rule is broken.
38
+ :type Violation Impact: str
39
+ :param Criticality: Severity of the rule (e.g., 'High', 'Mission Ending').
40
+ :type Criticality: str
41
+ """
42
+
43
+ DICT_VALID_KEYS = [
44
+ ('Rule ID', str),
45
+ ('Functional Category', str),
46
+ ('Maturity', str),
47
+ ('Title', str),
48
+ ('Description', str),
49
+ ('Violation Impact', str),
50
+ ('Criticality', str),
51
+ ]
52
+
53
+ TIME_FORMATS = {}
54
+
55
+ DJANGO_KEY_MAP = {
56
+ 'uuid': 'uuid',
57
+ 'rule_id': 'Rule ID',
58
+ 'functional_category': 'Functional Category',
59
+ 'maturity': 'Maturity',
60
+ 'title': 'Title',
61
+ 'description': 'Description',
62
+ 'impact_of_violation': 'Violation Impact',
63
+ 'criticality': 'Criticality',
64
+ }
65
+
66
+ def __sub_init(self):
67
+ """Internal initialization hook."""
68
+ return
69
+
70
+ def time(self):
71
+ """
72
+ [Disabled]
73
+
74
+ Planning rules are static constraints and do not have a single
75
+ point-in-time association.
76
+ """
77
+ return
78
+
79
+
80
+ class PlanningRuleContainer(DataContainer):
81
+ """
82
+ **The Concept:**
83
+ This container acts as a digital "Rulebook." When generating a mission
84
+ plan or analyzing a sequence of commands, this container is used to
85
+ look up the definitions and impacts of the rules being checked.
86
+
87
+ **Mission Specifics:**
88
+ While named for OCO-2, the structure follows the standard patterns
89
+ required for any mission using the Teamtools Studio planning validation
90
+ workflow.
91
+ """
92
+ NAME = 'Planning Rule'
93
+ DATA_ITEM_CLS = PlanningRuleItem
94
+ DO_NOT_DIFF = []
95
+
96
+ def __init__(self, **kwargs):
97
+ super().__init__(**kwargs)
98
+
99
+ @property
100
+ def repr_cols(self):
101
+ """
102
+ The list of columns intended for display in rule reports.
103
+ """
104
+ #this is here because it's an abstract method, which may
105
+ #not be useful. Consider removal
106
+ return self._repr_cols
@@ -0,0 +1,53 @@
1
+ from jpl_time import Time
2
+
3
+ from dexter.core.data import DataItem, DataContainer
4
+ from dexter.core.dispo import Dispositioner, dispo_method
5
+ from dexter.util.utilities import cached_property, filter_on_attr_regex
6
+
7
+ class TacoItem(DataItem):
8
+ @property
9
+ def valid(self):
10
+ # Just double-check we have the right object type
11
+ # TODO: Add additional validation checks here.
12
+ # This isn't good enough.
13
+ return isinstance(self.source, dict)
14
+
15
+ @property
16
+ def time(self):
17
+ return None
18
+
19
+ # @cached_property
20
+ # def start_time(self):
21
+ # return Time(self.source.get_start_lmst())
22
+
23
+ # @cached_property
24
+ # def end_time(self):
25
+ # return Time(self.source.get_end_lmst())
26
+
27
+ # @property
28
+ # def time(self):
29
+ # return self.start_time
30
+
31
+ def stamp(self, dispo_value):
32
+ pass
33
+
34
+ # @property
35
+ # def seq_id(self):
36
+ # return self.source.seq_id
37
+
38
+ class TacoContainer(DataContainer):
39
+ NAME = 'taco'
40
+ DATA_ITEM_CLS = TacoItem
41
+
42
+ # @classmethod
43
+ # def _format_data(cls, raw_data, copy_data, default_dispo):
44
+ # # Default is just to assume a list of data payloads that can be passed directly to the DataItem class
45
+ # return [cls.DATA_ITEM_CLS(_, copy_data=copy_data, default_dispo=default_dispo) for _ in raw_data.get_executions()]
46
+
47
+ # def get_exec_by_seq_id(self, seq_id):
48
+ # """Gets all executions of a particular Seq ID as a list, allowing regex"""
49
+ # return filter_on_attr_regex(self.data, seq_id=seq_id)
50
+
51
+ # def get_exec_by_seq_id_exact(self, seq_id):
52
+ # """Gets all executions of a particular SeqID as a list, exact match only (e.x. 'aut_00000' must match, version ignored)"""
53
+ # return [_ for _ in self.data if _.seq_id == seq_id]
File without changes
File without changes
@@ -0,0 +1,403 @@
1
+ #Standard Library Imports
2
+ import json
3
+ import pdb
4
+ import pandas as pd
5
+ from pathlib import Path
6
+
7
+ #Installed Library Importsimport pytest
8
+ from datetime import datetime
9
+ import pytest
10
+
11
+ #Teamtool Studio Imports
12
+ from tts_utilities.logger import create_logger
13
+ from tts_utilities.test_utilities import safe_test_paths
14
+
15
+ #This Library Imports
16
+ from tts_data_utils.core.generic import GenericContainer
17
+
18
+ logger = create_logger(f'tts_data_utils.core.data_container')
19
+ TEST_INPUT_DIR, TEST_OUTPUT_DIR = safe_test_paths(Path(__file__).parent, 'test_files/data_container')
20
+
21
+
22
+ @pytest.fixture
23
+ def csv_file(tmp_path):
24
+ path = tmp_path / "test_data.csv"
25
+ df = pd.DataFrame({"col1": [1, 2], "col2": ["a", "b"]})
26
+ df.to_csv(path, index=False)
27
+ return path
28
+
29
+ @pytest.fixture
30
+ def xlsx_file(tmp_path):
31
+ path = tmp_path / "test_data.xlsx"
32
+ df = pd.DataFrame({"col1": [3, 4], "col2": ["c", "d"]})
33
+ df.to_excel(path, index=False)
34
+ return path
35
+
36
+ @pytest.fixture
37
+ def nested_container():
38
+ inner = GenericContainer(raw_data=[{'a': 1}])
39
+ outer = GenericContainer(
40
+ raw_data=[{'id': 'parent'}],
41
+ subcontainers=[{'child_tab': inner}]
42
+ )
43
+ return outer
44
+ @pytest.fixture(scope="module")
45
+ def filter_container():
46
+ time = [datetime.fromtimestamp(ii) for ii in range(100)]
47
+ col1 = [ii for ii in range(50)]*2
48
+ col2 = [ii for ii in range(100, 200)]
49
+ col3 = [str(ii) for ii in range(200, 300)]
50
+ col4 = [ii for ii in range(300, 400)]
51
+ col5 = [ii for ii in range(400, 500)]
52
+ raw_data = [{
53
+ 'Time': t,
54
+ 'Column 1': c1,
55
+ 'Column 2': c2,
56
+ 'Column 3': c3,
57
+ 'Column 4': c4,
58
+ 'Column 5': c5,
59
+ } for t, c1, c2, c3, c4, c5 in zip(time, col1, col2, col3, col4, col5)]
60
+ filter_container = GenericContainer(raw_data=raw_data)
61
+ return filter_container
62
+
63
+ @pytest.fixture(scope="module")
64
+ def filter_container_case_sensitive():
65
+ #I totally could have merged this alongside the other container fixture
66
+ #but I didn't recognize the need for it until the tests that don't need
67
+ #case sensitivity were mature enough that I didn't want to merge them
68
+ #for essentially no value add.
69
+ time = [datetime.fromtimestamp(ii) for ii in range(100)]
70
+ col1 = [ii for ii in range(50)]*2
71
+ col2 = [ii for ii in range(100, 200)]
72
+ col3 = [str(ii) for ii in range(200, 300)]
73
+ col4 = [ii for ii in range(300, 400)]
74
+ col5 = [ii for ii in range(400, 500)]
75
+ col3[30] = 'This is a string that is sentence case'
76
+ col3[40] = 'This Is A String That Is Title Case'
77
+ col3[50] = 'This is A string That has Mixed case'
78
+ raw_data = [{
79
+ 'Time': t,
80
+ 'Column 1': c1,
81
+ 'Column 2': c2,
82
+ 'Column 3': c3,
83
+ 'Column 4': c4,
84
+ 'Column 5': c5,
85
+ } for t, c1, c2, c3, c4, c5 in zip(time, col1, col2, col3, col4, col5)]
86
+ filter_container = GenericContainer(raw_data=raw_data)
87
+ return filter_container
88
+
89
+ @pytest.fixture(scope="module")
90
+ def onchange_container():
91
+ time = [datetime.fromtimestamp(ii) for ii in range(100)]
92
+ col1 = [ii//10 for ii in range(100)]
93
+ col2 = [ii for ii in range(100, 200)]
94
+ col3 = [ii for ii in range(200, 300)]
95
+ col4 = [ii for ii in range(300, 400)]
96
+ col5 = [ii for ii in range(400, 500)]
97
+ raw_data = [{
98
+ 'Time': t,
99
+ 'Column 1': c1,
100
+ 'Column 2': c2,
101
+ 'Column 3': c3,
102
+ 'Column 4': c4,
103
+ 'Column 5': c5,
104
+ } for t, c1, c2, c3, c4, c5 in zip(time, col1, col2, col3, col4, col5)]
105
+ filter_container = GenericContainer(raw_data=raw_data)
106
+ return filter_container
107
+
108
+ class TestFiltering:
109
+ def test_gt(self, filter_container):
110
+ gt_container = filter_container.gt('Column 2', 125)
111
+ assert len(gt_container) == 74
112
+ assert [r['Column 1'] for r in gt_container] == ([ii for ii in range(50)]*2)[26:]
113
+ assert [r['Column 2'] for r in gt_container] == [ii for ii in range(126, 200)]
114
+ assert [r['Column 3'] for r in gt_container] == [str(ii) for ii in range(226, 300)]
115
+ assert [r['Column 4'] for r in gt_container] == [ii for ii in range(326, 400)]
116
+ assert [r['Column 5'] for r in gt_container] == [ii for ii in range(426, 500)]
117
+
118
+ def test_lt(self, filter_container):
119
+ lt_container = filter_container.lt('Column 2', 125)
120
+ assert len(lt_container) == 25
121
+ assert [r['Column 1'] for r in lt_container] == ([ii for ii in range(50)]*2)[:25]
122
+ assert [r['Column 2'] for r in lt_container] == [ii for ii in range(100, 125)]
123
+ assert [r['Column 3'] for r in lt_container] == [str(ii) for ii in range(200, 225)]
124
+ assert [r['Column 4'] for r in lt_container] == [ii for ii in range(300, 325)]
125
+ assert [r['Column 5'] for r in lt_container] == [ii for ii in range(400, 425)]
126
+
127
+ def test_gte(self, filter_container):
128
+ gte_container = filter_container.gte('Column 2', 125)
129
+ assert len(gte_container) == 75
130
+ assert [r['Column 1'] for r in gte_container] == ([ii for ii in range(50)]*2)[25:]
131
+ assert [r['Column 2'] for r in gte_container] == [ii for ii in range(125, 200)]
132
+ assert [r['Column 3'] for r in gte_container] == [str(ii) for ii in range(225, 300)]
133
+ assert [r['Column 4'] for r in gte_container] == [ii for ii in range(325, 400)]
134
+ assert [r['Column 5'] for r in gte_container] == [ii for ii in range(425, 500)]
135
+
136
+ def test_lte(self, filter_container):
137
+ lte_container = filter_container.lte('Column 2', 125)
138
+ assert len(lte_container) == 26
139
+ assert [r['Column 1'] for r in lte_container] == ([ii for ii in range(50)]*2)[:26]
140
+ assert [r['Column 2'] for r in lte_container] == [ii for ii in range(100, 126)]
141
+ assert [r['Column 3'] for r in lte_container] == [str(ii) for ii in range(200, 226)]
142
+ assert [r['Column 4'] for r in lte_container] == [ii for ii in range(300, 326)]
143
+ assert [r['Column 5'] for r in lte_container] == [ii for ii in range(400, 426)]
144
+
145
+ def test_eq(self, filter_container):
146
+ eq_container = filter_container.eq('Column 1', 32)
147
+ assert len(eq_container) == 2
148
+ assert [r['Column 1'] for r in eq_container] == [32, 32]
149
+ assert [r['Column 2'] for r in eq_container] == [132, 182]
150
+ assert [r['Column 3'] for r in eq_container] == ['232', '282']
151
+ assert [r['Column 4'] for r in eq_container] == [332, 382]
152
+ assert [r['Column 5'] for r in eq_container] == [432, 482]
153
+
154
+ def test_ne(self, filter_container):
155
+ ne_container = filter_container.ne('Column 1', 32)
156
+ assert len(ne_container) == 98
157
+ assert [r['Column 1'] for r in ne_container] == [ii for ii in range(50) if ii != 32]*2
158
+ assert [r['Column 2'] for r in ne_container] == [ii for ii in range(100, 200) if ii not in [132, 182]]
159
+ assert [r['Column 3'] for r in ne_container] == [str(ii) for ii in range(200, 300) if ii not in [232, 282]]
160
+ assert [r['Column 4'] for r in ne_container] == [ii for ii in range(300, 400) if ii not in [332, 382]]
161
+ assert [r['Column 5'] for r in ne_container] == [ii for ii in range(400, 500) if ii not in [432, 482]]
162
+
163
+ def test_isin(self, filter_container):
164
+ isin_container_int = filter_container.isin('Column 2', [10, 125, 150, 175, 200, 225, 'A', {}, []])
165
+ isin_container_str = filter_container.isin('Column 3', ['10', '225', '250', '275', '300', '325', 900, {}, []])
166
+
167
+ assert len(isin_container_int) == 3
168
+ assert [r['Column 1'] for r in isin_container_int] == [25, 0, 25]
169
+ assert [r['Column 2'] for r in isin_container_int] == [125, 150, 175]
170
+ assert [r['Column 3'] for r in isin_container_int] == ['225', '250', '275']
171
+ assert [r['Column 4'] for r in isin_container_int] == [325, 350, 375]
172
+ assert [r['Column 5'] for r in isin_container_int] == [425, 450, 475]
173
+
174
+ assert len(isin_container_str) == 3
175
+ assert [r['Column 1'] for r in isin_container_str] == [25, 0, 25]
176
+ assert [r['Column 2'] for r in isin_container_str] == [125, 150, 175]
177
+ assert [r['Column 3'] for r in isin_container_str] == ['225', '250', '275']
178
+ assert [r['Column 4'] for r in isin_container_str] == [325, 350, 375]
179
+ assert [r['Column 5'] for r in isin_container_str] == [425, 450, 475]
180
+
181
+ def test_notin(self, filter_container):
182
+ notin_container_int = filter_container.notin('Column 2', [10, 125, 150, 175, 200, 225, 'A', {}, []])
183
+ notin_container_str = filter_container.notin('Column 3', ['10', '225', '250', '275', '300', '325', 900, {}, []])
184
+
185
+ assert len(notin_container_int) == 97
186
+ assert [r['Column 1'] for r in notin_container_int] == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
187
+ assert [r['Column 2'] for r in notin_container_int] == [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199]
188
+ assert [r['Column 3'] for r in notin_container_int] == ['200', '201', '202', '203', '204', '205', '206', '207', '208', '209', '210', '211', '212', '213', '214', '215', '216', '217', '218', '219', '220', '221', '222', '223', '224', '226', '227', '228', '229', '230', '231', '232', '233', '234', '235', '236', '237', '238', '239', '240', '241', '242', '243', '244', '245', '246', '247', '248', '249', '251', '252', '253', '254', '255', '256', '257', '258', '259', '260', '261', '262', '263', '264', '265', '266', '267', '268', '269', '270', '271', '272', '273', '274', '276', '277', '278', '279', '280', '281', '282', '283', '284', '285', '286', '287', '288', '289', '290', '291', '292', '293', '294', '295', '296', '297', '298', '299']
189
+ assert [r['Column 4'] for r in notin_container_int] == [300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399]
190
+ assert [r['Column 5'] for r in notin_container_int] == [400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499]
191
+
192
+ assert len(notin_container_str) == 97
193
+ assert [r['Column 1'] for r in notin_container_str] == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
194
+ assert [r['Column 2'] for r in notin_container_str] == [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199]
195
+ assert [r['Column 3'] for r in notin_container_str] == ['200', '201', '202', '203', '204', '205', '206', '207', '208', '209', '210', '211', '212', '213', '214', '215', '216', '217', '218', '219', '220', '221', '222', '223', '224', '226', '227', '228', '229', '230', '231', '232', '233', '234', '235', '236', '237', '238', '239', '240', '241', '242', '243', '244', '245', '246', '247', '248', '249', '251', '252', '253', '254', '255', '256', '257', '258', '259', '260', '261', '262', '263', '264', '265', '266', '267', '268', '269', '270', '271', '272', '273', '274', '276', '277', '278', '279', '280', '281', '282', '283', '284', '285', '286', '287', '288', '289', '290', '291', '292', '293', '294', '295', '296', '297', '298', '299']
196
+ assert [r['Column 4'] for r in notin_container_str] == [300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399]
197
+ assert [r['Column 5'] for r in notin_container_str] == [400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499]
198
+
199
+
200
+ def test_contains_case_sensitive(self, filter_container_case_sensitive):
201
+ #There's no technical reason to separate this from test_contains.
202
+ #It's really just because I didn't recognize I needed this when I built
203
+ #the fixtures, and then when I decided to add these couple tests that
204
+ #care about sensitiviy/insensitivity, I didn't want to have to rewrite
205
+ #all the rest
206
+ contains_container_case_sensitive = filter_container_case_sensitive.contains('Column 3', 'This is a string')
207
+
208
+ assert len(contains_container_case_sensitive) == 1
209
+ assert [r['Column 1'] for r in contains_container_case_sensitive] == [30]
210
+ assert [r['Column 2'] for r in contains_container_case_sensitive] == [130]
211
+ assert [r['Column 3'] for r in contains_container_case_sensitive] == ['This is a string that is sentence case']
212
+ assert [r['Column 4'] for r in contains_container_case_sensitive] == [330]
213
+ assert [r['Column 5'] for r in contains_container_case_sensitive] == [430]
214
+
215
+ def test_doesnotcontain_case_sensitive(self, filter_container_case_sensitive):
216
+ #There's no technical reason to separate this from test_contains.
217
+ #It's really just because I didn't recognize I needed this when I built
218
+ #the fixtures, and then when I decided to add these couple tests that
219
+ #care about sensitiviy/insensitivity, I didn't want to have to rewrite
220
+ #all the rest
221
+ contains_container_case_insensitive = filter_container_case_sensitive.contains('Column 3', 'This is a string', case_sensitive=False)
222
+
223
+ assert len(contains_container_case_insensitive) == 3
224
+ assert [r['Column 1'] for r in contains_container_case_insensitive] == [30, 40, 0]
225
+ assert [r['Column 2'] for r in contains_container_case_insensitive] == [130, 140, 150]
226
+ assert [r['Column 3'] for r in contains_container_case_insensitive] == ['This is a string that is sentence case', 'This Is A String That Is Title Case', 'This is A string That has Mixed case']
227
+ assert [r['Column 4'] for r in contains_container_case_insensitive] == [330, 340, 350]
228
+ assert [r['Column 5'] for r in contains_container_case_insensitive] == [430, 440, 450]
229
+
230
+ def test_contains(self, filter_container):
231
+ contains_container = filter_container.contains('Column 3', '3')
232
+
233
+ assert len(contains_container) == 19
234
+ assert [r['Column 1'] for r in contains_container] == [3, 13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 43, 3, 13, 23, 33, 43]
235
+ assert [r['Column 2'] for r in contains_container] == [103, 113, 123, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 143, 153, 163, 173, 183, 193]
236
+ assert [r['Column 3'] for r in contains_container] == ['203', '213', '223', '230', '231', '232', '233', '234', '235', '236', '237', '238', '239', '243', '253', '263', '273', '283', '293']
237
+ assert [r['Column 4'] for r in contains_container] == [303, 313, 323, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 343, 353, 363, 373, 383, 393]
238
+ assert [r['Column 5'] for r in contains_container] == [403, 413, 423, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 443, 453, 463, 473, 483, 493]
239
+
240
+ def test_doesnotcontain(self, filter_container):
241
+ doesnotcontain_container = filter_container.doesnotcontain('Column 3', '3')
242
+
243
+ assert len(doesnotcontain_container) == 81
244
+ assert [r['Column 1'] for r in doesnotcontain_container] == [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29, 40, 41, 42, 44, 45, 46, 47, 48, 49, 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 45, 46, 47, 48, 49]
245
+ assert [r['Column 2'] for r in doesnotcontain_container] == [100, 101, 102, 104, 105, 106, 107, 108, 109, 110, 111, 112, 114, 115, 116, 117, 118, 119, 120, 121, 122, 124, 125, 126, 127, 128, 129, 140, 141, 142, 144, 145, 146, 147, 148, 149, 150, 151, 152, 154, 155, 156, 157, 158, 159, 160, 161, 162, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 184, 185, 186, 187, 188, 189, 190, 191, 192, 194, 195, 196, 197, 198, 199]
246
+ assert [r['Column 3'] for r in doesnotcontain_container] == ['200', '201', '202', '204', '205', '206', '207', '208', '209', '210', '211', '212', '214', '215', '216', '217', '218', '219', '220', '221', '222', '224', '225', '226', '227', '228', '229', '240', '241', '242', '244', '245', '246', '247', '248', '249', '250', '251', '252', '254', '255', '256', '257', '258', '259', '260', '261', '262', '264', '265', '266', '267', '268', '269', '270', '271', '272', '274', '275', '276', '277', '278', '279', '280', '281', '282', '284', '285', '286', '287', '288', '289', '290', '291', '292', '294', '295', '296', '297', '298', '299']
247
+ assert [r['Column 4'] for r in doesnotcontain_container] == [300, 301, 302, 304, 305, 306, 307, 308, 309, 310, 311, 312, 314, 315, 316, 317, 318, 319, 320, 321, 322, 324, 325, 326, 327, 328, 329, 340, 341, 342, 344, 345, 346, 347, 348, 349, 350, 351, 352, 354, 355, 356, 357, 358, 359, 360, 361, 362, 364, 365, 366, 367, 368, 369, 370, 371, 372, 374, 375, 376, 377, 378, 379, 380, 381, 382, 384, 385, 386, 387, 388, 389, 390, 391, 392, 394, 395, 396, 397, 398, 399]
248
+ assert [r['Column 5'] for r in doesnotcontain_container] == [400, 401, 402, 404, 405, 406, 407, 408, 409, 410, 411, 412, 414, 415, 416, 417, 418, 419, 420, 421, 422, 424, 425, 426, 427, 428, 429, 440, 441, 442, 444, 445, 446, 447, 448, 449, 450, 451, 452, 454, 455, 456, 457, 458, 459, 460, 461, 462, 464, 465, 466, 467, 468, 469, 470, 471, 472, 474, 475, 476, 477, 478, 479, 480, 481, 482, 484, 485, 486, 487, 488, 489, 490, 491, 492, 494, 495, 496, 497, 498, 499]
249
+
250
+ def test_before(self, filter_container):
251
+ before_container = filter_container.before(datetime.fromtimestamp(3), 'Time')
252
+ before_container_inclusive = filter_container.before(datetime.fromtimestamp(3), 'Time', inclusive=True)
253
+
254
+ assert len(before_container) == 3
255
+ assert [r['Column 1'] for r in before_container] == [0, 1, 2]
256
+ assert [r['Column 2'] for r in before_container] == [100, 101, 102]
257
+ assert [r['Column 3'] for r in before_container] == ['200', '201', '202']
258
+ assert [r['Column 4'] for r in before_container] == [300, 301, 302]
259
+ assert [r['Column 5'] for r in before_container] == [400, 401, 402]
260
+
261
+ assert len(before_container_inclusive) == 4
262
+ assert [r['Column 1'] for r in before_container_inclusive] == [0, 1, 2, 3]
263
+ assert [r['Column 2'] for r in before_container_inclusive] == [100, 101, 102, 103]
264
+ assert [r['Column 3'] for r in before_container_inclusive] == ['200', '201', '202', '203']
265
+ assert [r['Column 4'] for r in before_container_inclusive] == [300, 301, 302, 303]
266
+ assert [r['Column 5'] for r in before_container_inclusive] == [400, 401, 402, 403]
267
+
268
+ def test_after(self, filter_container):
269
+ after_container = filter_container.after(datetime.fromtimestamp(97), 'Time')
270
+ after_container_inclusive = filter_container.after(datetime.fromtimestamp(97), 'Time', inclusive=True)
271
+
272
+ assert len(after_container) == 2
273
+ assert [r['Column 1'] for r in after_container] == [48, 49]
274
+ assert [r['Column 2'] for r in after_container] == [198, 199]
275
+ assert [r['Column 3'] for r in after_container] == ['298', '299']
276
+ assert [r['Column 4'] for r in after_container] == [398, 399]
277
+ assert [r['Column 5'] for r in after_container] == [498, 499]
278
+
279
+ assert len(after_container_inclusive) == 3
280
+ assert [r['Column 1'] for r in after_container_inclusive] == [47, 48, 49]
281
+ assert [r['Column 2'] for r in after_container_inclusive] == [197, 198, 199]
282
+ assert [r['Column 3'] for r in after_container_inclusive] == ['297', '298', '299']
283
+ assert [r['Column 4'] for r in after_container_inclusive] == [397, 398, 399]
284
+ assert [r['Column 5'] for r in after_container_inclusive] == [497, 498, 499]
285
+
286
+ def test_between(self, filter_container):
287
+ between_container_both_inclusive = filter_container.between('Time', datetime.fromtimestamp(30), datetime.fromtimestamp(35))
288
+ between_container_lower_inclusive = filter_container.between('Time', datetime.fromtimestamp(30), datetime.fromtimestamp(35), inclusive='lower')
289
+ between_container_upper_inclusive = filter_container.between('Time', datetime.fromtimestamp(30), datetime.fromtimestamp(35), inclusive='upper')
290
+ between_container_neither_inclusive = filter_container.between('Time', datetime.fromtimestamp(30), datetime.fromtimestamp(35), inclusive='neither')
291
+
292
+ assert len(between_container_both_inclusive) == 6
293
+ assert [r['Column 1'] for r in between_container_both_inclusive] == [30, 31, 32, 33, 34, 35]
294
+ assert [r['Column 2'] for r in between_container_both_inclusive] == [130, 131, 132, 133, 134, 135]
295
+ assert [r['Column 3'] for r in between_container_both_inclusive] == ['230', '231', '232', '233', '234', '235']
296
+ assert [r['Column 4'] for r in between_container_both_inclusive] == [330, 331, 332, 333, 334, 335]
297
+ assert [r['Column 5'] for r in between_container_both_inclusive] == [430, 431, 432, 433, 434, 435]
298
+
299
+ assert len(between_container_lower_inclusive) == 5
300
+ assert [r['Column 1'] for r in between_container_lower_inclusive] == [30, 31, 32, 33, 34]
301
+ assert [r['Column 2'] for r in between_container_lower_inclusive] == [130, 131, 132, 133, 134]
302
+ assert [r['Column 3'] for r in between_container_lower_inclusive] == ['230', '231', '232', '233', '234']
303
+ assert [r['Column 4'] for r in between_container_lower_inclusive] == [330, 331, 332, 333, 334]
304
+ assert [r['Column 5'] for r in between_container_lower_inclusive] == [430, 431, 432, 433, 434]
305
+
306
+ assert len(between_container_upper_inclusive) == 5
307
+ assert [r['Column 1'] for r in between_container_upper_inclusive] == [31, 32, 33, 34, 35]
308
+ assert [r['Column 2'] for r in between_container_upper_inclusive] == [131, 132, 133, 134, 135]
309
+ assert [r['Column 3'] for r in between_container_upper_inclusive] == ['231', '232', '233', '234', '235']
310
+ assert [r['Column 4'] for r in between_container_upper_inclusive] == [331, 332, 333, 334, 335]
311
+ assert [r['Column 5'] for r in between_container_upper_inclusive] == [431, 432, 433, 434, 435]
312
+
313
+ assert len(between_container_neither_inclusive) == 4
314
+ assert [r['Column 1'] for r in between_container_neither_inclusive] == [31, 32, 33, 34]
315
+ assert [r['Column 2'] for r in between_container_neither_inclusive] == [131, 132, 133, 134]
316
+ assert [r['Column 3'] for r in between_container_neither_inclusive] == ['231', '232', '233', '234']
317
+ assert [r['Column 4'] for r in between_container_neither_inclusive] == [331, 332, 333, 334]
318
+ assert [r['Column 5'] for r in between_container_neither_inclusive] == [431, 432, 433, 434]
319
+
320
+ def test_matches(self, filter_container):
321
+ matches_container = filter_container.matches('Column 3', r'\d0\d')
322
+ assert [r['Column 1'] for r in matches_container] == [ii for ii in range(10)]
323
+ assert [r['Column 2'] for r in matches_container] == [ii for ii in range(100, 110)]
324
+ assert [r['Column 3'] for r in matches_container] == [str(ii) for ii in range(200, 210)]
325
+ assert [r['Column 4'] for r in matches_container] == [ii for ii in range(300, 310)]
326
+ assert [r['Column 5'] for r in matches_container] == [ii for ii in range(400, 410)]
327
+
328
+ def test_doesnotmatch(self, filter_container):
329
+ does_not_match_container = filter_container.doesnotmatch('Column 3', r'\d0\d')
330
+ assert [r['Column 1'] for r in does_not_match_container] == ([ii for ii in range(50)]*2)[10:]
331
+ assert [r['Column 2'] for r in does_not_match_container] == [ii for ii in range(100, 200)][10:]
332
+ assert [r['Column 3'] for r in does_not_match_container] == [str(ii) for ii in range(200, 300)][10:]
333
+ assert [r['Column 4'] for r in does_not_match_container] == [ii for ii in range(300, 400)][10:]
334
+ assert [r['Column 5'] for r in does_not_match_container] == [ii for ii in range(400, 500)][10:]
335
+
336
+ def test_onchange(self, onchange_container):
337
+ onchange_only = onchange_container.on_change('Column 1')
338
+ assert [r['Column 1'] for r in onchange_only] == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
339
+ assert [r['Column 2'] for r in onchange_only] == [100, 110, 120, 130, 140, 150, 160, 170, 180, 190]
340
+ assert [r['Column 3'] for r in onchange_only] == [200, 210, 220, 230, 240, 250, 260, 270, 280, 290]
341
+ assert [r['Column 4'] for r in onchange_only] == [300, 310, 320, 330, 340, 350, 360, 370, 380, 390]
342
+ assert [r['Column 5'] for r in onchange_only] == [400, 410, 420, 430, 440, 450, 460, 470, 480, 490]
343
+
344
+ def test_min(self, filter_container):
345
+ try:
346
+ filter_container.eq('Column 1', 1000, minimum=1)
347
+ except Exception as e:
348
+ assert str(e) == 'Filtered length is not at least 1 as specified'
349
+ assert len(filter_container.eq('Column 1', 10, minimum=2)) == 2
350
+
351
+ def test_max(self, filter_container):
352
+ try:
353
+ filter_container.eq('Column 1', 10, maximum=1)
354
+ except Exception as e:
355
+ assert str(e) == 'Filtered length is not less than or equal to 1 as specified'
356
+ assert len(filter_container.eq('Column 1', 10, maximum=2)) == 2
357
+
358
+ def test_exactly(self, filter_container):
359
+ try:
360
+ filter_container.eq('Column 1', 10, exactly=1)
361
+ except Exception as e:
362
+ assert str(e) == 'Filtered length is not exactly 1 as specified'
363
+ assert len(filter_container.eq('Column 1', 10, exactly=2)) == 2
364
+
365
+ class TestDataIO:
366
+ def test_init_from_csv(self, csv_file):
367
+ container = GenericContainer(csv_path=csv_file)
368
+ assert len(container) == 2
369
+ assert container[0]['col2'] == 'a'
370
+
371
+ def test_to_csv_with_mkdirs(self, filter_container, tmp_path):
372
+ deep_path = tmp_path / "new_dir" / "output.csv"
373
+ filter_container.to_csv(deep_path, mkdirs=True)
374
+ assert deep_path.exists()
375
+
376
+ def test_init_with_metadata_cleaning(self):
377
+ # Covers lines 126-127
378
+ meta = {'_internal': 'hide', 'public': 'show', 'dictionary': 'ignore'}
379
+ container = GenericContainer(raw_data=[{'a': 1}], metadata=meta)
380
+ assert 'public' in container.metadata
381
+ assert '_internal' not in container.metadata
382
+ assert 'dictionary' not in container.metadata
383
+
384
+ class TestReporting:
385
+ def test_summarize_feature(self, onchange_container):
386
+ # Fixes the xfailed test and covers lines 1116-1173
387
+ summary = onchange_container.summarize('Column 1')
388
+ assert len(summary) == 10 # 0 through 9
389
+ assert int(summary[0]['Occurances']) == 10
390
+ assert 'First Occurence' in summary[0].values
391
+
392
+ def test_power_table_nested(self, nested_container):
393
+ # Covers the recursive PowerTable calls (line 264)
394
+ table = nested_container.power_table()
395
+ html = table.render()
396
+ assert 'parent' in html
397
+ assert 'child_tab' in html
398
+
399
+ def test_repr_logic(self, filter_container):
400
+ # Covers lines 391-413
401
+ ascii_table = repr(filter_container)
402
+ assert '+' in ascii_table
403
+ assert 'Column 1' in ascii_table