faceberg 0.1.0__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.
@@ -0,0 +1,367 @@
1
+ """Tests for faceberg.config module."""
2
+
3
+ import pytest
4
+
5
+ from faceberg.config import Config, Dataset, Namespace, Node, Table, View
6
+
7
+
8
+ @pytest.fixture
9
+ def sample_config():
10
+ """Fixture that returns a fresh config dict for each test."""
11
+ return {
12
+ "ns1": {
13
+ "table1": {
14
+ "type": "dataset",
15
+ "repo": "org/dataset1",
16
+ "config": "config1",
17
+ },
18
+ "table2": {
19
+ "type": "dataset",
20
+ "repo": "org/dataset2",
21
+ "config": "config2",
22
+ },
23
+ },
24
+ "ns2": {
25
+ "view1": {
26
+ "type": "view",
27
+ "query": "SELECT * FROM ns1.table1",
28
+ },
29
+ },
30
+ "ns3": {
31
+ "subns1": {
32
+ "table3": {
33
+ "type": "dataset",
34
+ "repo": "org/dataset3",
35
+ "config": "config3",
36
+ }
37
+ }
38
+ },
39
+ }
40
+
41
+
42
+ def test_config(sample_config):
43
+ cfg = Config.from_dict(sample_config)
44
+
45
+ assert isinstance(cfg, Config)
46
+ for k, v in cfg.items():
47
+ assert isinstance(v, Namespace)
48
+
49
+ # Verify to_dict includes type discriminators
50
+ cfg_dict = cfg.to_dict()
51
+ assert cfg_dict["ns1"]["table1"]["type"] == "dataset"
52
+ assert cfg_dict["ns2"]["view1"]["type"] == "view"
53
+
54
+ # Verify data access
55
+ assert cfg[("ns1", "table1")].repo == "org/dataset1"
56
+ assert cfg[("ns1", "table2")].config == "config2"
57
+ assert cfg[("ns2", "view1")].query == "SELECT * FROM ns1.table1"
58
+ assert cfg[("ns3", "subns1", "table3")].repo == "org/dataset3"
59
+
60
+ # Verify mutation
61
+ cfg[("ns1", "table1")].repo = "org/updated_dataset1"
62
+ assert cfg[("ns1", "table1")].repo == "org/updated_dataset1"
63
+
64
+ # Check datasets() method
65
+ datasets = cfg.datasets()
66
+ assert datasets == {
67
+ ("ns1", "table1"): cfg[("ns1", "table1")],
68
+ ("ns1", "table2"): cfg[("ns1", "table2")],
69
+ ("ns3", "subns1", "table3"): cfg[("ns3", "subns1", "table3")],
70
+ }
71
+
72
+
73
+ def test_config_root(sample_config):
74
+ cfg = Config.from_dict(sample_config)
75
+ assert cfg[()] is cfg
76
+
77
+
78
+ def test_config_contains(sample_config):
79
+ """Test __contains__ method for membership testing."""
80
+ cfg = Config.from_dict(sample_config)
81
+
82
+ # Test with string keys
83
+ assert "ns1" in cfg
84
+ assert "ns2" in cfg
85
+ assert "nonexistent" not in cfg
86
+ assert ("ns1", "table1") in cfg
87
+ assert ("ns1", "table999") not in cfg
88
+
89
+ # Test with tuple keys
90
+ assert ("ns1",) in cfg
91
+ assert ("ns1", "table1") in cfg
92
+ assert ("ns3", "subns1", "table3") in cfg
93
+ assert ("ns1", "nonexistent") not in cfg
94
+ assert ("nonexistent", "table") not in cfg
95
+
96
+
97
+ def test_config_setitem_creates_intermediate_namespaces():
98
+ """Test that __setitem__ creates intermediate Namespace objects as needed."""
99
+ cfg = Config()
100
+
101
+ # Set nested item without creating intermediates first
102
+ new_dataset = Dataset(repo="org/new_dataset", config="new_config")
103
+ cfg[("analytics", "sales", "orders")] = new_dataset
104
+
105
+ # Verify intermediate namespaces were created
106
+ assert isinstance(cfg[("analytics",)], Namespace)
107
+ assert isinstance(cfg[("analytics", "sales")], Namespace)
108
+ assert cfg[("analytics", "sales", "orders")].repo == "org/new_dataset"
109
+
110
+
111
+ def test_config_setitem_overwrite(sample_config):
112
+ """Test overwriting existing items."""
113
+ cfg = Config.from_dict(sample_config)
114
+
115
+ # Overwrite existing dataset
116
+ new_dataset = Dataset(repo="org/replaced", config="new_config")
117
+ cfg[("ns1", "table1")] = new_dataset
118
+ assert cfg[("ns1", "table1")].repo == "org/replaced"
119
+
120
+ # Overwrite with different node type
121
+ new_view = View(query="SELECT * FROM replaced")
122
+ cfg["ns1.table1"] = new_view
123
+ assert isinstance(cfg["ns1.table1"], View)
124
+ assert cfg["ns1.table1"].query == "SELECT * FROM replaced"
125
+
126
+
127
+ def test_config_getitem_keyerror(sample_config):
128
+ """Test that accessing non-existent keys raises KeyError."""
129
+ cfg = Config.from_dict(sample_config)
130
+
131
+ with pytest.raises(KeyError):
132
+ _ = cfg["nonexistent"]
133
+
134
+ with pytest.raises(KeyError):
135
+ _ = cfg["ns1.nonexistent"]
136
+
137
+ with pytest.raises(KeyError):
138
+ _ = cfg[("nonexistent", "nested")]
139
+
140
+
141
+ def test_config_invalid_key_type(sample_config):
142
+ """Test that invalid key types raise TypeError."""
143
+ cfg = Config.from_dict(sample_config)
144
+
145
+ with pytest.raises(TypeError, match="Key must be a tuple or string"):
146
+ _ = cfg[123]
147
+
148
+ with pytest.raises(TypeError, match="Key must be a tuple or string"):
149
+ _ = cfg[["list", "key"]]
150
+
151
+ with pytest.raises(TypeError, match="Key must be a tuple or string"):
152
+ cfg[123] = Dataset(repo="test", config="default")
153
+
154
+
155
+ def test_config_empty():
156
+ """Test empty config initialization and operations."""
157
+ cfg = Config()
158
+ assert cfg.to_dict() == {}
159
+
160
+ # Add first item
161
+ cfg["first"] = Namespace()
162
+ assert "first" in cfg
163
+ assert isinstance(cfg["first"], Namespace)
164
+
165
+
166
+ def test_config_repr():
167
+ """Test Config string representation."""
168
+ cfg = Config()
169
+ cfg["test"] = Namespace()
170
+ repr_str = repr(cfg)
171
+ assert "Config" in repr_str
172
+
173
+
174
+ def test_yaml_round_trip(tmp_path, sample_config):
175
+ """Test saving and loading config from YAML file with type preservation."""
176
+ cfg = Config.from_dict(sample_config)
177
+ yaml_path = tmp_path / "test_config.yaml"
178
+
179
+ # Save to YAML
180
+ cfg.to_yaml(yaml_path)
181
+ assert yaml_path.exists()
182
+
183
+ # Verify YAML content has type discriminators
184
+ yaml_content = yaml_path.read_text()
185
+ assert "type: dataset" in yaml_content
186
+ assert "type: view" in yaml_content
187
+
188
+ # Load from YAML and verify round-trip
189
+ loaded_cfg = Config.from_yaml(yaml_path)
190
+ assert loaded_cfg.to_dict() == cfg.to_dict()
191
+
192
+ # Verify data integrity and types
193
+ assert isinstance(loaded_cfg[("ns1", "table1")], Dataset)
194
+ assert loaded_cfg[("ns1", "table1")].repo == "org/dataset1"
195
+ assert isinstance(loaded_cfg[("ns2", "view1")], View)
196
+ assert loaded_cfg[("ns2", "view1")].query == "SELECT * FROM ns1.table1"
197
+
198
+
199
+ def test_yaml_empty_file(tmp_path):
200
+ """Test loading from empty YAML file."""
201
+ yaml_path = tmp_path / "empty.yaml"
202
+ yaml_path.write_text("")
203
+
204
+ cfg = Config.from_yaml(yaml_path)
205
+ assert cfg.to_dict() == {}
206
+
207
+
208
+ def test_yaml_preserves_all_types(tmp_path):
209
+ """Test that YAML serialization preserves all node types."""
210
+ cfg = Config()
211
+ cfg[("data", "dataset1")] = Dataset(repo="org/repo1", config="cfg1")
212
+ cfg[("data", "view1")] = View(query="SELECT 1")
213
+ cfg[("data", "table1")] = Table(uri="")
214
+
215
+ yaml_path = tmp_path / "types.yaml"
216
+ cfg.to_yaml(yaml_path)
217
+
218
+ # Verify YAML has all type discriminators
219
+ yaml_content = yaml_path.read_text()
220
+ assert "type: dataset" in yaml_content
221
+ assert "type: view" in yaml_content
222
+ assert "type: table" in yaml_content
223
+
224
+ # Load and verify all types are preserved
225
+ loaded = Config.from_yaml(yaml_path)
226
+ assert isinstance(loaded[("data", "dataset1")], Dataset)
227
+ assert isinstance(loaded[("data", "view1")], View)
228
+ assert isinstance(loaded[("data", "table1")], Table)
229
+ assert loaded[("data", "dataset1")].repo == "org/repo1"
230
+ assert loaded[("data", "view1")].query == "SELECT 1"
231
+
232
+
233
+ def test_node_from_dict_table():
234
+ """Test Node.from_dict for Table type."""
235
+ data = {"type": "table", "uri": ""}
236
+ node = Node.from_dict(data)
237
+ assert isinstance(node, Table)
238
+
239
+
240
+ def test_node_from_dict_dataset():
241
+ """Test Node.from_dict for Dataset type."""
242
+ data = {"type": "dataset", "repo": "org/dataset", "config": "default"}
243
+ node = Node.from_dict(data)
244
+ assert isinstance(node, Dataset)
245
+ assert node.repo == "org/dataset"
246
+ assert node.config == "default"
247
+
248
+
249
+ def test_node_from_dict_dataset_default_config():
250
+ """Test Dataset with default config value."""
251
+ data = {"type": "dataset", "repo": "org/dataset"}
252
+ node = Node.from_dict(data)
253
+ assert isinstance(node, Dataset)
254
+ assert node.repo == "org/dataset"
255
+ assert node.config == "default" # Default value
256
+
257
+
258
+ def test_node_from_dict_view():
259
+ """Test Node.from_dict for View type."""
260
+ data = {"type": "view", "query": "SELECT * FROM table"}
261
+ node = Node.from_dict(data)
262
+ assert isinstance(node, View)
263
+ assert node.query == "SELECT * FROM table"
264
+
265
+
266
+ def test_node_from_dict_namespace():
267
+ """Test Node.from_dict for Namespace type (explicit and implicit)."""
268
+ # Explicit type
269
+ data = {"type": "namespace", "child1": {"type": "table", "uri": ""}}
270
+ node = Node.from_dict(data)
271
+ assert isinstance(node, Namespace)
272
+ assert isinstance(node["child1"], Table)
273
+
274
+ # Implicit type (no type field defaults to namespace)
275
+ data = {"child1": {"type": "table", "uri": ""}, "child2": {"type": "view", "query": "SELECT 1"}}
276
+ node = Node.from_dict(data)
277
+ assert isinstance(node, Namespace)
278
+ assert isinstance(node["child1"], Table)
279
+ assert isinstance(node["child2"], View)
280
+
281
+
282
+ def test_node_from_dict_unknown_type():
283
+ """Test Node.from_dict raises ValueError for unknown types."""
284
+ data = {"type": "unknown_type"}
285
+ with pytest.raises(ValueError, match="Unknown node type: unknown_type"):
286
+ Node.from_dict(data)
287
+
288
+
289
+ def test_node_from_dict_invalid_input():
290
+ """Test Node.from_dict raises TypeError for non-dict input."""
291
+ with pytest.raises(TypeError, match="Expected dict to deserialize"):
292
+ Node.from_dict("not a dict")
293
+
294
+ with pytest.raises(TypeError, match="Expected dict to deserialize"):
295
+ Node.from_dict(123)
296
+
297
+ with pytest.raises(TypeError, match="Expected dict to deserialize"):
298
+ Node.from_dict(["list", "input"])
299
+
300
+
301
+ def test_node_to_dict():
302
+ """Test Node.to_dict for different node types includes type discriminators."""
303
+ dataset = Dataset(repo="org/repo", config="cfg")
304
+ assert dataset.to_dict() == {"repo": "org/repo", "config": "cfg", "type": "dataset"}
305
+
306
+ view = View(query="SELECT * FROM table")
307
+ assert view.to_dict() == {"query": "SELECT * FROM table", "type": "view"}
308
+
309
+ table = Table(uri="file://data/location")
310
+ assert table.to_dict() == {"uri": "file://data/location", "type": "table"}
311
+
312
+
313
+ def test_namespace_repr():
314
+ """Test Namespace string representation."""
315
+ ns = Namespace()
316
+ ns["child"] = Table(uri="")
317
+ repr_str = repr(ns)
318
+ assert "Namespace" in repr_str
319
+
320
+
321
+ def test_namespace_dict_behavior():
322
+ """Test that Namespace behaves like a dict."""
323
+ ns = Namespace()
324
+ ns["key1"] = Dataset(repo="org/repo1", config="cfg1")
325
+ ns["key2"] = View(query="SELECT 1")
326
+
327
+ assert len(ns) == 2
328
+ assert "key1" in ns
329
+ assert "key2" in ns
330
+ assert list(ns.keys()) == ["key1", "key2"]
331
+
332
+
333
+ def test_config_from_dict_with_empty():
334
+ """Test Config.from_dict with empty dict creates empty config."""
335
+ cfg = Config.from_dict({})
336
+ assert cfg.to_dict() == {}
337
+
338
+
339
+ def test_complex_nested_structure():
340
+ """Test deeply nested namespace structure."""
341
+ cfg = Config()
342
+ cfg[("level1", "level2", "level3", "level4", "dataset")] = Dataset(
343
+ repo="org/deep", config="nested"
344
+ )
345
+
346
+ assert isinstance(cfg[("level1",)], Namespace)
347
+ assert isinstance(cfg[("level1", "level2")], Namespace)
348
+ assert isinstance(cfg[("level1", "level2", "level3")], Namespace)
349
+ assert isinstance(cfg[("level1", "level2", "level3", "level4")], Namespace)
350
+ assert cfg[("level1", "level2", "level3", "level4", "dataset")].repo == "org/deep"
351
+
352
+
353
+ def test_mixed_access_patterns(sample_config):
354
+ """Test mixing tuple and string access in same config."""
355
+ cfg = Config.from_dict(sample_config)
356
+
357
+ # Access same item with different patterns
358
+ assert cfg[("ns1", "table1")] is cfg[("ns1", "table1")]
359
+ assert cfg[("ns3", "subns1")] is cfg[("ns3", "subns1")]
360
+
361
+ # Set with tuple, read with string
362
+ cfg[("new", "item")] = Dataset(repo="org/test", config="default")
363
+ assert cfg[("new", "item")].repo == "org/test"
364
+
365
+ # Set with string, read with tuple
366
+ cfg[("another", "item")] = View(query="SELECT 2")
367
+ assert cfg[("another", "item")].query == "SELECT 2"