masster 0.4.20__py3-none-any.whl → 0.4.22__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.

Potentially problematic release.


This version of masster might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: masster
3
- Version: 0.4.20
3
+ Version: 0.4.22
4
4
  Summary: Mass spectrometry data analysis package
5
5
  Project-URL: homepage, https://github.com/zamboni-lab/masster
6
6
  Project-URL: repository, https://github.com/zamboni-lab/masster
@@ -1,8 +1,8 @@
1
- masster/__init__.py,sha256=Btr_yVIY2S4gI0t5f2ZgIRFPiBBDJOq9Jqf9lh8PWx4,775
2
- masster/_version.py,sha256=nrWkv_1h3nB0Kn1uxpIVJdwPz73mGUWmFDSEREXTW4Y,257
1
+ masster/__init__.py,sha256=ueZ224WPNRRjQEYTaQUol818nwQgJwB93HbEfmtPRmg,1041
2
+ masster/_version.py,sha256=vQFUBi9UR5DFflCbwobRTLg-jW9TsSQB1GlM1tfxYuc,257
3
3
  masster/chromatogram.py,sha256=iYpdv8C17zVnlWvOFgAn9ns2uFGiF-GgoYf5QVVAbHs,19319
4
4
  masster/logger.py,sha256=tR65N23zfrNpcZNbZm2ot_Aual9XrGB1MWjLrovZkMs,16749
5
- masster/spectrum.py,sha256=_upC_g2N9gwTaflXAugs9pSXpKUmzbIehofDordk7WI,47718
5
+ masster/spectrum.py,sha256=XJSUrqXZSzfpWnD8v5IMClXMRZLKLYIk014qaMOS9_k,49738
6
6
  masster/data/dda/20250530_VH_IQX_KW_RP_HSST3_100mm_12min_pos_v4_DDA_OT_C-MiLUT_QC_dil2_01_20250602151849.sample5,sha256=LdJMF8uLoDm9ixZNHBoOzBH6hX7NGY7vTvqa2Pzetb8,6539174
7
7
  masster/data/dda/20250530_VH_IQX_KW_RP_HSST3_100mm_12min_pos_v4_DDA_OT_C-MiLUT_QC_dil3_01_20250602150634.sample5,sha256=hWUfslGoOTiQw59jENSBXP4sa6DdkbOi40FJ68ep61Q,6956773
8
8
  masster/data/dda/20250530_VH_IQX_KW_RP_HSST3_100mm_12min_pos_v4_MS1_C-MiLUT_C008_v6_r38_01.sample5,sha256=dSd2cIgYYdRcNSzkhqlZCeWKi3x8Hhhcx8BFMuiVG4c,11382948
@@ -18,17 +18,17 @@ masster/lib/__init__.py,sha256=TcePNx3SYZHz6763TL9Sg4gUNXaRWjlrOtyS6vsu-hg,178
18
18
  masster/lib/lib.py,sha256=mxUYBCBmkSBZB82557smSHCS25BAusuCewvW8zwsLGg,27130
19
19
  masster/sample/__init__.py,sha256=HL0m1ept0PMAYUCQtDDnkdOS12IFl6oLAq4TZQz83uY,170
20
20
  masster/sample/adducts.py,sha256=jdtkkiMFeObRv3myluUx--IfpRsEq3-hPgkCb2VUxy4,32590
21
- masster/sample/h5.py,sha256=7NJeErlIHwC2Qh3nchusLZJWjBGue9zExAx08C89qhg,111889
21
+ masster/sample/h5.py,sha256=B0gAmhrnoFoybotqsqiT8s-PkeZWUdIQfI-4cnM52Zc,115430
22
22
  masster/sample/helpers.py,sha256=JhzFpNh7j7YVUibIMuPQ50hBcGDEBCaBbmwA3Z5OhgM,41336
23
23
  masster/sample/lib.py,sha256=E-j9c3Wd8f9a-H8xj7CAOwlA8KcyXPoFyYm3c8r7LtI,33755
24
- masster/sample/load.py,sha256=Hc0ZZWZS719JOdDmx-NaPP1yHKf_peSoBpbNRUz2UfA,51168
24
+ masster/sample/load.py,sha256=TqBuDPrNgIotR-mL475AfDZntsW0JjYcy5AovVKRZxY,51560
25
25
  masster/sample/parameters.py,sha256=Gg2KcuNbV_wZ_Wwv93QlM5J19ji0oSIvZLPV1NoBmq0,4456
26
- masster/sample/plot.py,sha256=abLnG0Bk75vqSGQz6uA3uTK3IE9N-s687ZH-n8Mhdzg,82757
27
- masster/sample/processing.py,sha256=lCHRv290oAFOxe_zR5GMi4FdxodjJh1rj2uLWy_wHnc,49771
26
+ masster/sample/plot.py,sha256=bQlEXCLQQWpUV88vGtftW9WAVwDKZ4k7TBirV9cO54w,82573
27
+ masster/sample/processing.py,sha256=A1u5u7lGG0HR_ciUhJFmmwgugher7_AZQopNnbu65Bs,55910
28
28
  masster/sample/quant.py,sha256=tHNjvUFTdehKR31BXBZnVsBxMD9XJHgaltITOjr71uE,7562
29
- masster/sample/sample.py,sha256=uibBjBlH97CP7Mx8HoFdmHMj6Re_vvHUuaYmlp5TEmI,19715
30
- masster/sample/sample5_schema.json,sha256=voVB6z0TaIJwU-_SPUEYWKH7mKC16ycTe1nW6gODYP8,3916
31
- masster/sample/save.py,sha256=XZl5ITYdOjojYFOoUZ-0ygVSPH1kT5Va6e8NyuTRNAI,32500
29
+ masster/sample/sample.py,sha256=uQP5DLdsRSC2YwZZvspsL9rgl_HefB-oxrL2dpgg_fc,19788
30
+ masster/sample/sample5_schema.json,sha256=H5e2T6rHIDzul2kp_yP-ILUUWUpW08wP2pEQjMR0nSk,3977
31
+ masster/sample/save.py,sha256=2yQtcQcRJjgAKPImTydj7LpyyMop_Q9JKRlNEK4yU6k,36339
32
32
  masster/sample/sciex.py,sha256=vnbxsq_qnAQVuzcpziP1o3IC4kM5amGBcPmC2TAuDLw,46319
33
33
  masster/sample/defaults/__init__.py,sha256=A09AOP44cxD_oYohyt7XFUho0zndRcrzVD4DUaGnKH4,447
34
34
  masster/sample/defaults/find_adducts_def.py,sha256=Bu2KiBJRxD0SAnOPNMm_Nk-6fx6QYoRXjFNGzz-0_o0,13570
@@ -37,18 +37,18 @@ masster/sample/defaults/find_ms2_def.py,sha256=KTELMAnioGLYbhzAwOgK14TZqboPEvzeB
37
37
  masster/sample/defaults/get_spectrum_def.py,sha256=o62p31PhGd-LiIkTOzKQhwPtnO2AtQDHcPu-O-YoQPs,11460
38
38
  masster/sample/defaults/sample_def.py,sha256=keoXyMyrm_iLgbYqfIbqCpJ3XHBVlNwCNmb5iMQL0iY,14579
39
39
  masster/study/__init__.py,sha256=55axdFuqRX4aXtJ8ocnhcLB32fNtmmJpCi58moO0r4g,237
40
- masster/study/export.py,sha256=L-YOUGSgVeTprYnrQxaN0qC8MRqUdDQ0D4o2h7HLi2Q,54813
41
- masster/study/h5.py,sha256=LiVGUAtULyPpZIUmKVJSaV38huJb8FsKOUWBOqiv0QU,82363
42
- masster/study/helpers.py,sha256=UJgwMBvkhiMHV1XAn2qtGVaaBPW_r7P7Mv0uoyqRbCs,160219
40
+ masster/study/export.py,sha256=c-UQPYRwNBde8E1cYOB-0ZZz2tBDTwglRMlPfSKYB0w,59291
41
+ masster/study/h5.py,sha256=eINlVmcJuntwbkkZHwzm10c63Kg7zib49vkzLDj1PyU,84790
42
+ masster/study/helpers.py,sha256=6nDTNlsZbZWf9L6D5qzK2TUO2y7UBq51Ftj8N4bkIAk,160260
43
43
  masster/study/id.py,sha256=6NUBBKZCFOU1wlDKM0eXQeOIStSZCRNJ_3x7ZaIHzmM,55263
44
- masster/study/load.py,sha256=CQQY_7BzagE3oQTdDlqNyfuMdVWIAft-M4a2WCFnxp0,70695
45
- masster/study/merge.py,sha256=39RA7kpok1nlfYsXKpNqETdxJnv2H0TtyexV2nkSgoc,158699
44
+ masster/study/load.py,sha256=mI6UyErlj3vIzSuG93fOjsxA7IIDCaiKfcuAcc2538o,72425
45
+ masster/study/merge.py,sha256=3R_Dg6l2mnJUu3gFVAgrAN5hFSQyfHbqYPmc2cUfJqQ,159232
46
46
  masster/study/parameters.py,sha256=0elaF7YspTsB7qyajWAbRNL2VfKlGz5GJLifmO8IGkk,3276
47
- masster/study/plot.py,sha256=OW_9d6hp4Oq1B9qpZHSU-wO-AkAmmGSZZfUIvLMpwk0,88629
48
- masster/study/processing.py,sha256=u1MSRKTzcqHNz_dClSUSfgTxkNRdBLXtVyO5LXuW_uk,41031
49
- masster/study/save.py,sha256=YCvp4xhnG16sNXaT2mFDBoCrIMub0Es61B97qLo0maw,6705
50
- masster/study/study.py,sha256=UX07uyCEzdLSHJvp6bxavrNsd5m5zqkzTTCAc7QM5P8,39694
51
- masster/study/study5_schema.json,sha256=c0w24QdHak01m04I1VPu97KvF2468FcaqROhf6pmLk4,7507
47
+ masster/study/plot.py,sha256=OGUa_dDTD2QydbLg-4APRZc7Jx1kk9eXC9-GOLLgI1I,87666
48
+ masster/study/processing.py,sha256=p0d-DyxA0YI6K9OPQZYTEs00DC6obr6-kLHPVWljEO0,56437
49
+ masster/study/save.py,sha256=BANh9F1s-q7MclO1Mq_-v4xQyHeloEgmoPgRDVc-9aE,9037
50
+ masster/study/study.py,sha256=rk-pJNg80N6xbROa9fqPfwVxFgzL_FLoSUNOTYeD5E0,40116
51
+ masster/study/study5_schema.json,sha256=ghBeAXFS4a4Uavdn6TUVs9GaR1QOTnADCjQTOkN0tjU,7563
52
52
  masster/study/defaults/__init__.py,sha256=m3Z5KXGqsTdh7GjYzZoENERt39yRg0ceVRV1DeCt1P0,610
53
53
  masster/study/defaults/align_def.py,sha256=hHQbGgsOqMRHHr0Wn8Onr8XeaRz3-fFE0qGE-OMst80,20324
54
54
  masster/study/defaults/export_def.py,sha256=eXl3h4aoLX88XkHTpqahLd-QZ2gjUqrmjq8IJULXeWo,1203
@@ -62,13 +62,11 @@ masster/study/defaults/integrate_def.py,sha256=Vf4SAzdBfnsSZ3IRaF0qZvWu3gMDPHdgP
62
62
  masster/study/defaults/merge_def.py,sha256=K7sfwEGfgcWU85zorbWNFaxDhqRH52pxQoKv9Jn2qhY,15030
63
63
  masster/study/defaults/study_def.py,sha256=h8dYbi9xv0sesCSQik49Z53IkskMmNtW6ixl7it5pL0,16033
64
64
  masster/wizard/README.md,sha256=mL1A3YWJZOefpJ6D0-HqGLkVRmUlOpwyVFdvJBeeoZM,14149
65
- masster/wizard/__init__.py,sha256=Jw585YKI02VabRKUG8PhyU-lBqceZK_TIl4WBS4dvlM,530
65
+ masster/wizard/__init__.py,sha256=a2hcZnHASjfuw1lqZhZnvTR58rc33rRnoGAY_JfvGhI,683
66
66
  masster/wizard/example.py,sha256=xEZFTH9UZ8HKOm6s3JL8Js0Uw5ChnISWBHSZCL32vsM,7983
67
- masster/wizard/test_structure.py,sha256=h88gsYYCG6iDRjqPZC_r1H1T8y79j0E-K6OrwuHaSCU,1586
68
- masster/wizard/test_wizard.py,sha256=CMp1cpjH3iYYC5Fy6puF_K0kfwwk3bgOsSbUGW-t7Xk,8986
69
- masster/wizard/wizard.py,sha256=wVm0QJnV7am5gBxVUrqXX2UIZehnD1bq1C1mohLrsZ4,105884
70
- masster-0.4.20.dist-info/METADATA,sha256=8l19L39xZFXnBteH7thb8cZXl8jd1mfx8o2S6KKVyac,44207
71
- masster-0.4.20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
72
- masster-0.4.20.dist-info/entry_points.txt,sha256=ZHguQ_vPmdbpqq2uGtmEOLJfgP-DQ1T0c07Lxh30wc8,58
73
- masster-0.4.20.dist-info/licenses/LICENSE,sha256=bx5iLIKjgAdYQ7sISn7DsfHRKkoCUm1154sJJKhgqnU,35184
74
- masster-0.4.20.dist-info/RECORD,,
67
+ masster/wizard/wizard.py,sha256=esgaifLRyaGxytif9qOkTy-21VxlUQxrvl47K-l-BpE,37666
68
+ masster-0.4.22.dist-info/METADATA,sha256=CXrrzzCC5cZ_G9plLZyCtiNpTXevD0wPuUNm0mIy-a4,44207
69
+ masster-0.4.22.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
70
+ masster-0.4.22.dist-info/entry_points.txt,sha256=ZHguQ_vPmdbpqq2uGtmEOLJfgP-DQ1T0c07Lxh30wc8,58
71
+ masster-0.4.22.dist-info/licenses/LICENSE,sha256=bx5iLIKjgAdYQ7sISn7DsfHRKkoCUm1154sJJKhgqnU,35184
72
+ masster-0.4.22.dist-info/RECORD,,
@@ -1,49 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Simple test to verify the wizard module structure works correctly.
4
- """
5
-
6
- def test_wizard_module_import():
7
- """Test that the wizard module can be imported."""
8
- try:
9
- # Test direct wizard module import
10
- import sys
11
- from pathlib import Path
12
-
13
- # Add the masster directory to path
14
- masster_path = Path(__file__).parent.parent
15
- sys.path.insert(0, str(masster_path))
16
-
17
- # Import wizard directly from its module
18
- from wizard import Wizard, wizard_def
19
-
20
- print("✅ Successfully imported Wizard from wizard module")
21
- print(f"✅ wizard_def class available: {wizard_def}")
22
- print(f"✅ Wizard class available: {Wizard}")
23
-
24
- # Test creating wizard_def instance
25
- defaults = wizard_def(
26
- data_source="/test/data",
27
- study_folder="/test/output",
28
- polarity="positive"
29
- )
30
-
31
- print(f"✅ Created wizard_def instance with polarity: {defaults.polarity}")
32
- print(f"✅ Default adducts: {defaults.adducts}")
33
-
34
- return True
35
-
36
- except Exception as e:
37
- print(f"❌ Import failed: {e}")
38
- import traceback
39
- traceback.print_exc()
40
- return False
41
-
42
- if __name__ == "__main__":
43
- success = test_wizard_module_import()
44
- print("\n" + "="*50)
45
- if success:
46
- print("🎉 WIZARD MODULE STRUCTURE TEST PASSED!")
47
- else:
48
- print("❌ WIZARD MODULE STRUCTURE TEST FAILED!")
49
- print("="*50)
@@ -1,285 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Test script for the Wizard class.
4
-
5
- This script tests the basic functionality of the Wizard class without
6
- requiring actual raw data files.
7
- """
8
-
9
- import tempfile
10
- from pathlib import Path
11
- import sys
12
-
13
- # Add masster to path if needed
14
- sys.path.insert(0, str(Path(__file__).parent.parent))
15
-
16
- from masster import Wizard, wizard_def
17
-
18
-
19
- def test_wizard_initialization():
20
- """Test wizard initialization and parameter handling."""
21
- print("Testing Wizard initialization...")
22
-
23
- with tempfile.TemporaryDirectory() as temp_dir:
24
- temp_path = Path(temp_dir)
25
- data_source = temp_path / "data"
26
- study_folder = temp_path / "study"
27
-
28
- # Create directories
29
- data_source.mkdir()
30
-
31
- # Test basic initialization
32
- wizard = Wizard(
33
- data_source=str(data_source),
34
- study_folder=str(study_folder),
35
- polarity="positive",
36
- num_cores=2
37
- )
38
-
39
- assert wizard.polarity == "positive"
40
- assert wizard.params.num_cores == 2
41
- assert len(wizard.adducts) > 0 # Should have default adducts
42
- assert study_folder.exists() # Should create output directory
43
-
44
- print("✅ Basic initialization works")
45
-
46
- # Test parameter validation
47
- try:
48
- Wizard(
49
- data_source="", # Empty data source should fail
50
- study_folder=str(study_folder)
51
- )
52
- assert False, "Should have failed with empty data_source"
53
- except ValueError:
54
- print("✅ Parameter validation works")
55
-
56
- # Test custom parameters
57
- custom_params = wizard_def(
58
- data_source=str(data_source),
59
- study_folder=str(study_folder / "custom"),
60
- polarity="negative",
61
- num_cores=4,
62
- adducts=["H-1:-:1.0", "Cl:-:0.1"],
63
- batch_size=2,
64
- generate_plots=False
65
- )
66
-
67
- custom_wizard = Wizard(params=custom_params)
68
- assert custom_wizard.polarity == "negative"
69
- assert custom_wizard.params.batch_size == 2
70
- assert not custom_wizard.params.generate_plots
71
-
72
- print("✅ Custom parameters work")
73
-
74
-
75
- def test_file_discovery():
76
- """Test file discovery functionality."""
77
- print("\nTesting file discovery...")
78
-
79
- with tempfile.TemporaryDirectory() as temp_dir:
80
- temp_path = Path(temp_dir)
81
- data_source = temp_path / "data"
82
- study_folder = temp_path / "study"
83
-
84
- # Create test directory structure
85
- data_source.mkdir()
86
- (data_source / "subdir").mkdir()
87
-
88
- # Create mock files
89
- test_files = [
90
- "sample1.wiff",
91
- "sample2.raw",
92
- "sample3.mzML",
93
- "blank.wiff", # Should be skipped
94
- "QC_test.raw", # Should be skipped
95
- "subdir/sample4.wiff",
96
- ]
97
-
98
- for filename in test_files:
99
- file_path = data_source / filename
100
- file_path.parent.mkdir(parents=True, exist_ok=True)
101
- file_path.write_text("mock file content")
102
-
103
- # Create wizard
104
- wizard = Wizard(
105
- data_source=str(data_source),
106
- study_folder=str(study_folder),
107
- polarity="positive"
108
- )
109
-
110
- # Test file discovery
111
- found_files = wizard.discover_files()
112
- found_names = [f.name for f in found_files]
113
-
114
- # Should find sample files but skip blanks and QC
115
- assert "sample1.wiff" in found_names
116
- assert "sample2.raw" in found_names
117
- assert "sample3.mzML" in found_names
118
- assert "sample4.wiff" in found_names # From subdirectory
119
- assert "blank.wiff" not in found_names # Should be skipped
120
- assert "QC_test.raw" not in found_names # Should be skipped
121
-
122
- print(f"✅ Found {len(found_files)} files, correctly filtered")
123
-
124
- # Test without subdirectory search
125
- wizard.params.search_subfolders = False
126
- found_files_no_sub = wizard.discover_files()
127
- found_names_no_sub = [f.name for f in found_files_no_sub]
128
-
129
- assert "sample4.wiff" not in found_names_no_sub # Should not find in subdir
130
- assert len(found_files_no_sub) < len(found_files)
131
-
132
- print("✅ Subdirectory search control works")
133
-
134
-
135
- def test_wizard_status():
136
- """Test status monitoring and checkpointing."""
137
- print("\nTesting status monitoring...")
138
-
139
- with tempfile.TemporaryDirectory() as temp_dir:
140
- temp_path = Path(temp_dir)
141
- data_source = temp_path / "data"
142
- study_folder = temp_path / "study"
143
-
144
- data_source.mkdir()
145
-
146
- wizard = Wizard(
147
- data_source=str(data_source),
148
- study_folder=str(study_folder),
149
- polarity="positive"
150
- )
151
-
152
- # Test initial status
153
- status = wizard.get_status()
154
- assert status["current_step"] == "initialized"
155
- assert status["processed_files"] == 0
156
- assert not status["study_loaded"]
157
-
158
- print("✅ Initial status correct")
159
-
160
- # Test status update
161
- wizard.current_step = "converting_to_sample5"
162
- wizard.processed_files = ["file1.wiff", "file2.raw"]
163
-
164
- status = wizard.get_status()
165
- assert status["current_step"] == "converting_to_sample5"
166
- assert status["processed_files"] == 2
167
-
168
- print("✅ Status updates work")
169
-
170
- # Test checkpoint save/load
171
- wizard._save_checkpoint()
172
- checkpoint_file = wizard.checkpoint_file
173
- assert checkpoint_file.exists()
174
-
175
- print("✅ Checkpoint saving works")
176
-
177
- # Create new wizard and test checkpoint loading
178
- new_wizard = Wizard(
179
- data_source=str(data_source),
180
- study_folder=str(study_folder),
181
- polarity="positive",
182
- resume_enabled=True
183
- )
184
-
185
- # Should load from checkpoint
186
- assert len(new_wizard.processed_files) == 2
187
- assert new_wizard.current_step == "converting_to_sample5"
188
-
189
- print("✅ Checkpoint loading works")
190
-
191
-
192
- def test_defaults_and_validation():
193
- """Test default parameter classes and validation."""
194
- print("\nTesting parameter defaults and validation...")
195
-
196
- # Test wizard_def defaults
197
- defaults = wizard_def()
198
-
199
- # Should set polarity-specific adducts
200
- assert len(defaults.adducts) > 0
201
-
202
- # Test polarity switching
203
- neg_defaults = wizard_def(polarity="negative")
204
- pos_defaults = wizard_def(polarity="positive")
205
-
206
- # Should have different adducts
207
- assert neg_defaults.adducts != pos_defaults.adducts
208
-
209
- print("✅ Polarity-specific defaults work")
210
-
211
- # Test parameter validation
212
- defaults = wizard_def(
213
- data_source="/test/path",
214
- study_folder="/test/output",
215
- num_cores=999 # Should be capped to available cores
216
- )
217
-
218
- import multiprocessing
219
- max_cores = multiprocessing.cpu_count()
220
- assert defaults.num_cores <= max_cores
221
-
222
- print("✅ Parameter validation works")
223
-
224
-
225
- def test_logging_setup():
226
- """Test logging configuration."""
227
- print("\nTesting logging setup...")
228
-
229
- with tempfile.TemporaryDirectory() as temp_dir:
230
- temp_path = Path(temp_dir)
231
- data_source = temp_path / "data"
232
- study_folder = temp_path / "study"
233
-
234
- data_source.mkdir()
235
-
236
- wizard = Wizard(
237
- data_source=str(data_source),
238
- study_folder=str(study_folder),
239
- polarity="positive",
240
- log_to_file=True,
241
- log_level="DEBUG"
242
- )
243
-
244
- # Test logging
245
- wizard._log_progress("Test message")
246
-
247
- # Check log files exist
248
- assert wizard.log_file.exists()
249
-
250
- # Check log content
251
- log_content = wizard.log_file.read_text()
252
- assert "Test message" in log_content
253
-
254
- print("✅ Logging setup works")
255
-
256
-
257
- def main():
258
- """Run all tests."""
259
- print("=" * 50)
260
- print("WIZARD CLASS TESTS")
261
- print("=" * 50)
262
-
263
- try:
264
- test_wizard_initialization()
265
- test_file_discovery()
266
- test_wizard_status()
267
- test_defaults_and_validation()
268
- test_logging_setup()
269
-
270
- print("\n" + "=" * 50)
271
- print("🎉 ALL TESTS PASSED!")
272
- print("=" * 50)
273
-
274
- except Exception as e:
275
- print(f"\n❌ TEST FAILED: {e}")
276
- import traceback
277
- traceback.print_exc()
278
- return False
279
-
280
- return True
281
-
282
-
283
- if __name__ == "__main__":
284
- success = main()
285
- sys.exit(0 if success else 1)