zexus 1.6.2
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.
- package/LICENSE +0 -0
- package/README.md +2513 -0
- package/bin/zexus +2 -0
- package/bin/zpics +2 -0
- package/bin/zpm +2 -0
- package/bin/zx +2 -0
- package/bin/zx-deploy +2 -0
- package/bin/zx-dev +2 -0
- package/bin/zx-run +2 -0
- package/package.json +66 -0
- package/scripts/README.md +24 -0
- package/scripts/postinstall.js +44 -0
- package/shared_config.json +24 -0
- package/src/README.md +1525 -0
- package/src/tests/run_zexus_tests.py +117 -0
- package/src/tests/test_all_phases.zx +346 -0
- package/src/tests/test_blockchain_features.zx +306 -0
- package/src/tests/test_complexity_features.zx +321 -0
- package/src/tests/test_core_integration.py +185 -0
- package/src/tests/test_phase10_ecosystem.zx +177 -0
- package/src/tests/test_phase1_modifiers.zx +87 -0
- package/src/tests/test_phase2_plugins.zx +80 -0
- package/src/tests/test_phase3_security.zx +97 -0
- package/src/tests/test_phase4_vfs.zx +116 -0
- package/src/tests/test_phase5_types.zx +117 -0
- package/src/tests/test_phase6_metaprogramming.zx +125 -0
- package/src/tests/test_phase7_optimization.zx +132 -0
- package/src/tests/test_phase9_advanced_types.zx +157 -0
- package/src/tests/test_security_features.py +419 -0
- package/src/tests/test_security_features.zx +276 -0
- package/src/tests/test_simple_zx.zx +1 -0
- package/src/tests/test_verification_simple.zx +69 -0
- package/src/zexus/__init__.py +28 -0
- package/src/zexus/__main__.py +5 -0
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/advanced_types.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/builtin_modules.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/complexity_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/concurrency_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/config.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/dependency_injection.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/ecosystem.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/hybrid_orchestrator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/metaprogramming.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/optimization.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/plugin_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/policy_engine.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/stdlib_integration.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/strategy_recovery.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/type_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/virtual_filesystem.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +401 -0
- package/src/zexus/blockchain/__init__.py +40 -0
- package/src/zexus/blockchain/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/crypto.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/ledger.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/transaction.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/crypto.py +463 -0
- package/src/zexus/blockchain/ledger.py +255 -0
- package/src/zexus/blockchain/transaction.py +267 -0
- package/src/zexus/builtin_modules.py +284 -0
- package/src/zexus/builtin_plugins.py +317 -0
- package/src/zexus/capability_system.py +372 -0
- package/src/zexus/cli/__init__.py +2 -0
- package/src/zexus/cli/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +707 -0
- package/src/zexus/cli/zpm.py +203 -0
- package/src/zexus/compare_interpreter_compiler.py +146 -0
- package/src/zexus/compiler/__init__.py +169 -0
- package/src/zexus/compiler/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/bytecode.py +266 -0
- package/src/zexus/compiler/compat_runtime.py +277 -0
- package/src/zexus/compiler/lexer.py +257 -0
- package/src/zexus/compiler/parser.py +779 -0
- package/src/zexus/compiler/semantic.py +118 -0
- package/src/zexus/compiler/zexus_ast.py +454 -0
- package/src/zexus/complexity_system.py +575 -0
- package/src/zexus/concurrency_system.py +493 -0
- package/src/zexus/config.py +201 -0
- package/src/zexus/crypto_bridge.py +19 -0
- package/src/zexus/dependency_injection.py +423 -0
- package/src/zexus/ecosystem.py +434 -0
- package/src/zexus/environment.py +101 -0
- package/src/zexus/environment_manager.py +119 -0
- package/src/zexus/error_reporter.py +314 -0
- package/src/zexus/evaluator/__init__.py +12 -0
- package/src/zexus/evaluator/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/integration.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +700 -0
- package/src/zexus/evaluator/core.py +891 -0
- package/src/zexus/evaluator/expressions.py +827 -0
- package/src/zexus/evaluator/functions.py +3989 -0
- package/src/zexus/evaluator/integration.py +396 -0
- package/src/zexus/evaluator/statements.py +4303 -0
- package/src/zexus/evaluator/utils.py +126 -0
- package/src/zexus/evaluator_original.py +2041 -0
- package/src/zexus/external_bridge.py +16 -0
- package/src/zexus/find_affected_imports.sh +155 -0
- package/src/zexus/hybrid_orchestrator.py +152 -0
- package/src/zexus/input_validation.py +259 -0
- package/src/zexus/lexer.py +571 -0
- package/src/zexus/logging.py +89 -0
- package/src/zexus/lsp/__init__.py +9 -0
- package/src/zexus/lsp/completion_provider.py +207 -0
- package/src/zexus/lsp/definition_provider.py +22 -0
- package/src/zexus/lsp/hover_provider.py +71 -0
- package/src/zexus/lsp/server.py +269 -0
- package/src/zexus/lsp/symbol_provider.py +31 -0
- package/src/zexus/metaprogramming.py +321 -0
- package/src/zexus/module_cache.py +89 -0
- package/src/zexus/module_manager.py +107 -0
- package/src/zexus/object.py +973 -0
- package/src/zexus/optimization.py +424 -0
- package/src/zexus/parser/__init__.py +31 -0
- package/src/zexus/parser/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/integration.py +86 -0
- package/src/zexus/parser/parser.py +3977 -0
- package/src/zexus/parser/strategy_context.py +7254 -0
- package/src/zexus/parser/strategy_structural.py +1033 -0
- package/src/zexus/persistence.py +391 -0
- package/src/zexus/plugin_system.py +290 -0
- package/src/zexus/policy_engine.py +365 -0
- package/src/zexus/profiler/__init__.py +5 -0
- package/src/zexus/profiler/profiler.py +233 -0
- package/src/zexus/purity_system.py +398 -0
- package/src/zexus/runtime/__init__.py +20 -0
- package/src/zexus/runtime/async_runtime.py +324 -0
- package/src/zexus/search_old_imports.sh +65 -0
- package/src/zexus/security.py +1407 -0
- package/src/zexus/stack_trace.py +233 -0
- package/src/zexus/stdlib/__init__.py +27 -0
- package/src/zexus/stdlib/blockchain.py +341 -0
- package/src/zexus/stdlib/compression.py +167 -0
- package/src/zexus/stdlib/crypto.py +124 -0
- package/src/zexus/stdlib/datetime.py +163 -0
- package/src/zexus/stdlib/db_mongo.py +199 -0
- package/src/zexus/stdlib/db_mysql.py +162 -0
- package/src/zexus/stdlib/db_postgres.py +163 -0
- package/src/zexus/stdlib/db_sqlite.py +133 -0
- package/src/zexus/stdlib/encoding.py +230 -0
- package/src/zexus/stdlib/fs.py +195 -0
- package/src/zexus/stdlib/http.py +219 -0
- package/src/zexus/stdlib/http_server.py +248 -0
- package/src/zexus/stdlib/json_module.py +61 -0
- package/src/zexus/stdlib/math.py +360 -0
- package/src/zexus/stdlib/os_module.py +265 -0
- package/src/zexus/stdlib/regex.py +148 -0
- package/src/zexus/stdlib/sockets.py +253 -0
- package/src/zexus/stdlib/test_framework.zx +208 -0
- package/src/zexus/stdlib/test_runner.zx +119 -0
- package/src/zexus/stdlib_integration.py +341 -0
- package/src/zexus/strategy_recovery.py +256 -0
- package/src/zexus/syntax_validator.py +356 -0
- package/src/zexus/testing/zpics.py +407 -0
- package/src/zexus/testing/zpics_runtime.py +369 -0
- package/src/zexus/type_system.py +374 -0
- package/src/zexus/validation_system.py +569 -0
- package/src/zexus/virtual_filesystem.py +355 -0
- package/src/zexus/vm/__init__.py +8 -0
- package/src/zexus/vm/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/memory_manager.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/memory_pool.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/peephole_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/profiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/register_allocator.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/register_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/ssa_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +420 -0
- package/src/zexus/vm/bytecode.py +428 -0
- package/src/zexus/vm/bytecode_converter.py +297 -0
- package/src/zexus/vm/cache.py +532 -0
- package/src/zexus/vm/jit.py +720 -0
- package/src/zexus/vm/memory_manager.py +520 -0
- package/src/zexus/vm/memory_pool.py +511 -0
- package/src/zexus/vm/optimizer.py +478 -0
- package/src/zexus/vm/parallel_vm.py +899 -0
- package/src/zexus/vm/peephole_optimizer.py +452 -0
- package/src/zexus/vm/profiler.py +527 -0
- package/src/zexus/vm/register_allocator.py +462 -0
- package/src/zexus/vm/register_vm.py +520 -0
- package/src/zexus/vm/ssa_converter.py +757 -0
- package/src/zexus/vm/vm.py +1392 -0
- package/src/zexus/zexus_ast.py +1782 -0
- package/src/zexus/zexus_token.py +253 -0
- package/src/zexus/zpm/__init__.py +15 -0
- package/src/zexus/zpm/installer.py +116 -0
- package/src/zexus/zpm/package_manager.py +208 -0
- package/src/zexus/zpm/publisher.py +98 -0
- package/src/zexus/zpm/registry.py +110 -0
- package/src/zexus.egg-info/PKG-INFO +2235 -0
- package/src/zexus.egg-info/SOURCES.txt +876 -0
- package/src/zexus.egg-info/dependency_links.txt +1 -0
- package/src/zexus.egg-info/entry_points.txt +3 -0
- package/src/zexus.egg-info/not-zip-safe +1 -0
- package/src/zexus.egg-info/requires.txt +14 -0
- package/src/zexus.egg-info/top_level.txt +2 -0
- package/zexus.json +14 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Compression module for Zexus standard library."""
|
|
2
|
+
|
|
3
|
+
import zlib
|
|
4
|
+
import gzip
|
|
5
|
+
import bz2
|
|
6
|
+
import lzma
|
|
7
|
+
import base64
|
|
8
|
+
from typing import Dict, Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CompressionModule:
|
|
12
|
+
"""Provides compression/decompression operations."""
|
|
13
|
+
|
|
14
|
+
# Zlib compression
|
|
15
|
+
@staticmethod
|
|
16
|
+
def zlib_compress(data: str, level: int = 6) -> str:
|
|
17
|
+
"""Compress string using zlib (returns base64)."""
|
|
18
|
+
compressed = zlib.compress(data.encode(), level)
|
|
19
|
+
return base64.b64encode(compressed).decode('ascii')
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def zlib_decompress(data: str) -> str:
|
|
23
|
+
"""Decompress zlib data (from base64)."""
|
|
24
|
+
compressed = base64.b64decode(data)
|
|
25
|
+
return zlib.decompress(compressed).decode('utf-8')
|
|
26
|
+
|
|
27
|
+
# Gzip compression
|
|
28
|
+
@staticmethod
|
|
29
|
+
def gzip_compress(data: str, level: int = 6) -> str:
|
|
30
|
+
"""Compress string using gzip (returns base64)."""
|
|
31
|
+
compressed = gzip.compress(data.encode(), compresslevel=level)
|
|
32
|
+
return base64.b64encode(compressed).decode('ascii')
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def gzip_decompress(data: str) -> str:
|
|
36
|
+
"""Decompress gzip data (from base64)."""
|
|
37
|
+
compressed = base64.b64decode(data)
|
|
38
|
+
return gzip.decompress(compressed).decode('utf-8')
|
|
39
|
+
|
|
40
|
+
# Bzip2 compression
|
|
41
|
+
@staticmethod
|
|
42
|
+
def bzip2_compress(data: str, level: int = 9) -> str:
|
|
43
|
+
"""Compress string using bzip2 (returns base64)."""
|
|
44
|
+
compressed = bz2.compress(data.encode(), compresslevel=level)
|
|
45
|
+
return base64.b64encode(compressed).decode('ascii')
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def bzip2_decompress(data: str) -> str:
|
|
49
|
+
"""Decompress bzip2 data (from base64)."""
|
|
50
|
+
compressed = base64.b64decode(data)
|
|
51
|
+
return bz2.decompress(compressed).decode('utf-8')
|
|
52
|
+
|
|
53
|
+
# LZMA compression
|
|
54
|
+
@staticmethod
|
|
55
|
+
def lzma_compress(data: str, preset: int = 6) -> str:
|
|
56
|
+
"""Compress string using LZMA (returns base64)."""
|
|
57
|
+
compressed = lzma.compress(data.encode(), preset=preset)
|
|
58
|
+
return base64.b64encode(compressed).decode('ascii')
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def lzma_decompress(data: str) -> str:
|
|
62
|
+
"""Decompress LZMA data (from base64)."""
|
|
63
|
+
compressed = base64.b64decode(data)
|
|
64
|
+
return lzma.decompress(compressed).decode('utf-8')
|
|
65
|
+
|
|
66
|
+
# File compression helpers
|
|
67
|
+
@staticmethod
|
|
68
|
+
def compress_file(input_path: str, output_path: str, method: str = 'gzip', level: int = 6) -> Dict[str, Any]:
|
|
69
|
+
"""Compress file using specified method."""
|
|
70
|
+
try:
|
|
71
|
+
with open(input_path, 'rb') as f_in:
|
|
72
|
+
data = f_in.read()
|
|
73
|
+
|
|
74
|
+
if method == 'gzip':
|
|
75
|
+
with gzip.open(output_path, 'wb', compresslevel=level) as f_out:
|
|
76
|
+
f_out.write(data)
|
|
77
|
+
elif method == 'bzip2':
|
|
78
|
+
with bz2.open(output_path, 'wb', compresslevel=level) as f_out:
|
|
79
|
+
f_out.write(data)
|
|
80
|
+
elif method == 'lzma':
|
|
81
|
+
with lzma.open(output_path, 'wb', preset=level) as f_out:
|
|
82
|
+
f_out.write(data)
|
|
83
|
+
elif method == 'zlib':
|
|
84
|
+
compressed = zlib.compress(data, level)
|
|
85
|
+
with open(output_path, 'wb') as f_out:
|
|
86
|
+
f_out.write(compressed)
|
|
87
|
+
else:
|
|
88
|
+
return {'success': False, 'error': f'Unknown compression method: {method}'}
|
|
89
|
+
|
|
90
|
+
import os
|
|
91
|
+
original_size = os.path.getsize(input_path)
|
|
92
|
+
compressed_size = os.path.getsize(output_path)
|
|
93
|
+
ratio = (1 - compressed_size / original_size) * 100 if original_size > 0 else 0
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
'success': True,
|
|
97
|
+
'original_size': original_size,
|
|
98
|
+
'compressed_size': compressed_size,
|
|
99
|
+
'compression_ratio': round(ratio, 2)
|
|
100
|
+
}
|
|
101
|
+
except Exception as e:
|
|
102
|
+
return {'success': False, 'error': str(e)}
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def decompress_file(input_path: str, output_path: str, method: str = 'gzip') -> Dict[str, Any]:
|
|
106
|
+
"""Decompress file using specified method."""
|
|
107
|
+
try:
|
|
108
|
+
if method == 'gzip':
|
|
109
|
+
with gzip.open(input_path, 'rb') as f_in:
|
|
110
|
+
data = f_in.read()
|
|
111
|
+
elif method == 'bzip2':
|
|
112
|
+
with bz2.open(input_path, 'rb') as f_in:
|
|
113
|
+
data = f_in.read()
|
|
114
|
+
elif method == 'lzma':
|
|
115
|
+
with lzma.open(input_path, 'rb') as f_in:
|
|
116
|
+
data = f_in.read()
|
|
117
|
+
elif method == 'zlib':
|
|
118
|
+
with open(input_path, 'rb') as f_in:
|
|
119
|
+
data = zlib.decompress(f_in.read())
|
|
120
|
+
else:
|
|
121
|
+
return {'success': False, 'error': f'Unknown decompression method: {method}'}
|
|
122
|
+
|
|
123
|
+
with open(output_path, 'wb') as f_out:
|
|
124
|
+
f_out.write(data)
|
|
125
|
+
|
|
126
|
+
import os
|
|
127
|
+
return {
|
|
128
|
+
'success': True,
|
|
129
|
+
'decompressed_size': os.path.getsize(output_path)
|
|
130
|
+
}
|
|
131
|
+
except Exception as e:
|
|
132
|
+
return {'success': False, 'error': str(e)}
|
|
133
|
+
|
|
134
|
+
# Compression ratio calculation
|
|
135
|
+
@staticmethod
|
|
136
|
+
def calculate_ratio(original: str, compressed: str) -> float:
|
|
137
|
+
"""Calculate compression ratio percentage."""
|
|
138
|
+
if len(original) == 0:
|
|
139
|
+
return 0.0
|
|
140
|
+
return (1 - len(compressed) / len(original)) * 100
|
|
141
|
+
|
|
142
|
+
# CRC32 checksum for integrity
|
|
143
|
+
@staticmethod
|
|
144
|
+
def crc32(data: str) -> int:
|
|
145
|
+
"""Calculate CRC32 checksum."""
|
|
146
|
+
return zlib.crc32(data.encode()) & 0xffffffff
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def adler32(data: str) -> int:
|
|
150
|
+
"""Calculate Adler32 checksum."""
|
|
151
|
+
return zlib.adler32(data.encode()) & 0xffffffff
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# Export functions for easy access
|
|
155
|
+
zlib_compress = CompressionModule.zlib_compress
|
|
156
|
+
zlib_decompress = CompressionModule.zlib_decompress
|
|
157
|
+
gzip_compress = CompressionModule.gzip_compress
|
|
158
|
+
gzip_decompress = CompressionModule.gzip_decompress
|
|
159
|
+
bzip2_compress = CompressionModule.bzip2_compress
|
|
160
|
+
bzip2_decompress = CompressionModule.bzip2_decompress
|
|
161
|
+
lzma_compress = CompressionModule.lzma_compress
|
|
162
|
+
lzma_decompress = CompressionModule.lzma_decompress
|
|
163
|
+
compress_file = CompressionModule.compress_file
|
|
164
|
+
decompress_file = CompressionModule.decompress_file
|
|
165
|
+
calculate_ratio = CompressionModule.calculate_ratio
|
|
166
|
+
crc32 = CompressionModule.crc32
|
|
167
|
+
adler32 = CompressionModule.adler32
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Crypto module for Zexus standard library."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import secrets
|
|
5
|
+
import hmac
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CryptoModule:
|
|
9
|
+
"""Provides cryptographic operations."""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def hash_sha256(data: str) -> str:
|
|
13
|
+
"""Calculate SHA-256 hash."""
|
|
14
|
+
return hashlib.sha256(data.encode()).hexdigest()
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def hash_sha512(data: str) -> str:
|
|
18
|
+
"""Calculate SHA-512 hash."""
|
|
19
|
+
return hashlib.sha512(data.encode()).hexdigest()
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def hash_md5(data: str) -> str:
|
|
23
|
+
"""Calculate MD5 hash (not recommended for security)."""
|
|
24
|
+
return hashlib.md5(data.encode()).hexdigest()
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def hash_blake2b(data: str, digest_size: int = 32) -> str:
|
|
28
|
+
"""Calculate BLAKE2b hash."""
|
|
29
|
+
return hashlib.blake2b(data.encode(), digest_size=digest_size).hexdigest()
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def hash_blake2s(data: str, digest_size: int = 32) -> str:
|
|
33
|
+
"""Calculate BLAKE2s hash."""
|
|
34
|
+
return hashlib.blake2s(data.encode(), digest_size=digest_size).hexdigest()
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def hmac_sha256(data: str, key: str) -> str:
|
|
38
|
+
"""Calculate HMAC-SHA256."""
|
|
39
|
+
return hmac.new(key.encode(), data.encode(), hashlib.sha256).hexdigest()
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def hmac_sha512(data: str, key: str) -> str:
|
|
43
|
+
"""Calculate HMAC-SHA512."""
|
|
44
|
+
return hmac.new(key.encode(), data.encode(), hashlib.sha512).hexdigest()
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def random_bytes(size: int = 32) -> str:
|
|
48
|
+
"""Generate random bytes (hex encoded)."""
|
|
49
|
+
return secrets.token_hex(size)
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def random_int(min_val: int = 0, max_val: int = 2**32) -> int:
|
|
53
|
+
"""Generate random integer."""
|
|
54
|
+
return secrets.randbelow(max_val - min_val) + min_val
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def compare_digest(a: str, b: str) -> bool:
|
|
58
|
+
"""Constant-time string comparison."""
|
|
59
|
+
return hmac.compare_digest(a, b)
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def keccak256(data: str) -> str:
|
|
63
|
+
"""Calculate Keccak-256 hash (Ethereum-style)."""
|
|
64
|
+
try:
|
|
65
|
+
from Crypto.Hash import keccak
|
|
66
|
+
k = keccak.new(digest_bits=256)
|
|
67
|
+
k.update(data.encode())
|
|
68
|
+
return k.hexdigest()
|
|
69
|
+
except ImportError:
|
|
70
|
+
# Fallback to SHA3-256 if pycryptodome not available
|
|
71
|
+
return hashlib.sha3_256(data.encode()).hexdigest()
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def sha3_256(data: str) -> str:
|
|
75
|
+
"""Calculate SHA3-256 hash."""
|
|
76
|
+
return hashlib.sha3_256(data.encode()).hexdigest()
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def sha3_512(data: str) -> str:
|
|
80
|
+
"""Calculate SHA3-512 hash."""
|
|
81
|
+
return hashlib.sha3_512(data.encode()).hexdigest()
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def pbkdf2(password: str, salt: str, iterations: int = 100000,
|
|
85
|
+
key_length: int = 32, algorithm: str = 'sha256') -> str:
|
|
86
|
+
"""Derive key from password using PBKDF2."""
|
|
87
|
+
hash_name = algorithm.lower().replace('-', '')
|
|
88
|
+
derived_key = hashlib.pbkdf2_hmac(
|
|
89
|
+
hash_name,
|
|
90
|
+
password.encode(),
|
|
91
|
+
salt.encode(),
|
|
92
|
+
iterations,
|
|
93
|
+
dklen=key_length
|
|
94
|
+
)
|
|
95
|
+
return derived_key.hex()
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def generate_salt(size: int = 16) -> str:
|
|
99
|
+
"""Generate random salt."""
|
|
100
|
+
return secrets.token_hex(size)
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def constant_time_compare(a: str, b: str) -> bool:
|
|
104
|
+
"""Constant-time comparison (alias for compare_digest)."""
|
|
105
|
+
return hmac.compare_digest(a, b)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# Export functions for easy access
|
|
109
|
+
hash_sha256 = CryptoModule.hash_sha256
|
|
110
|
+
hash_sha512 = CryptoModule.hash_sha512
|
|
111
|
+
hash_md5 = CryptoModule.hash_md5
|
|
112
|
+
hash_blake2b = CryptoModule.hash_blake2b
|
|
113
|
+
hash_blake2s = CryptoModule.hash_blake2s
|
|
114
|
+
hmac_sha256 = CryptoModule.hmac_sha256
|
|
115
|
+
hmac_sha512 = CryptoModule.hmac_sha512
|
|
116
|
+
random_bytes = CryptoModule.random_bytes
|
|
117
|
+
random_int = CryptoModule.random_int
|
|
118
|
+
compare_digest = CryptoModule.compare_digest
|
|
119
|
+
keccak256 = CryptoModule.keccak256
|
|
120
|
+
sha3_256 = CryptoModule.sha3_256
|
|
121
|
+
sha3_512 = CryptoModule.sha3_512
|
|
122
|
+
pbkdf2 = CryptoModule.pbkdf2
|
|
123
|
+
generate_salt = CryptoModule.generate_salt
|
|
124
|
+
constant_time_compare = CryptoModule.constant_time_compare
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""DateTime module for Zexus standard library."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timedelta, timezone
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DateTimeModule:
|
|
9
|
+
"""Provides date and time operations."""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def now() -> datetime:
|
|
13
|
+
"""Get current datetime."""
|
|
14
|
+
return datetime.now()
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def utc_now() -> datetime:
|
|
18
|
+
"""Get current UTC datetime."""
|
|
19
|
+
return datetime.now(timezone.utc)
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def timestamp() -> float:
|
|
23
|
+
"""Get current Unix timestamp."""
|
|
24
|
+
return time.time()
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def from_timestamp(ts: float, utc: bool = False) -> datetime:
|
|
28
|
+
"""Create datetime from Unix timestamp."""
|
|
29
|
+
if utc:
|
|
30
|
+
return datetime.fromtimestamp(ts, timezone.utc)
|
|
31
|
+
return datetime.fromtimestamp(ts)
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def parse(date_string: str, format: str = '%Y-%m-%d %H:%M:%S') -> datetime:
|
|
35
|
+
"""Parse date string to datetime."""
|
|
36
|
+
return datetime.strptime(date_string, format)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def format(dt: datetime, format: str = '%Y-%m-%d %H:%M:%S') -> str:
|
|
40
|
+
"""Format datetime to string."""
|
|
41
|
+
return dt.strftime(format)
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def iso_format(dt: datetime) -> str:
|
|
45
|
+
"""Format datetime to ISO 8601 string."""
|
|
46
|
+
return dt.isoformat()
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def to_dict(dt: datetime) -> Dict[str, Any]:
|
|
50
|
+
"""Convert datetime to dictionary."""
|
|
51
|
+
return {
|
|
52
|
+
'year': dt.year,
|
|
53
|
+
'month': dt.month,
|
|
54
|
+
'day': dt.day,
|
|
55
|
+
'hour': dt.hour,
|
|
56
|
+
'minute': dt.minute,
|
|
57
|
+
'second': dt.second,
|
|
58
|
+
'microsecond': dt.microsecond,
|
|
59
|
+
'weekday': dt.weekday(),
|
|
60
|
+
'timestamp': dt.timestamp()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def add_days(dt: datetime, days: int) -> datetime:
|
|
65
|
+
"""Add days to datetime."""
|
|
66
|
+
return dt + timedelta(days=days)
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def add_hours(dt: datetime, hours: int) -> datetime:
|
|
70
|
+
"""Add hours to datetime."""
|
|
71
|
+
return dt + timedelta(hours=hours)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def add_minutes(dt: datetime, minutes: int) -> datetime:
|
|
75
|
+
"""Add minutes to datetime."""
|
|
76
|
+
return dt + timedelta(minutes=minutes)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def add_seconds(dt: datetime, seconds: int) -> datetime:
|
|
80
|
+
"""Add seconds to datetime."""
|
|
81
|
+
return dt + timedelta(seconds=seconds)
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def diff_days(dt1: datetime, dt2: datetime) -> int:
|
|
85
|
+
"""Get difference in days between two datetimes."""
|
|
86
|
+
return (dt2 - dt1).days
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def diff_seconds(dt1: datetime, dt2: datetime) -> float:
|
|
90
|
+
"""Get difference in seconds between two datetimes."""
|
|
91
|
+
return (dt2 - dt1).total_seconds()
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def is_before(dt1: datetime, dt2: datetime) -> bool:
|
|
95
|
+
"""Check if dt1 is before dt2."""
|
|
96
|
+
return dt1 < dt2
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def is_after(dt1: datetime, dt2: datetime) -> bool:
|
|
100
|
+
"""Check if dt1 is after dt2."""
|
|
101
|
+
return dt1 > dt2
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def is_between(dt: datetime, start: datetime, end: datetime) -> bool:
|
|
105
|
+
"""Check if datetime is between start and end."""
|
|
106
|
+
return start <= dt <= end
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def start_of_day(dt: datetime) -> datetime:
|
|
110
|
+
"""Get start of day (00:00:00)."""
|
|
111
|
+
return dt.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def end_of_day(dt: datetime) -> datetime:
|
|
115
|
+
"""Get end of day (23:59:59)."""
|
|
116
|
+
return dt.replace(hour=23, minute=59, second=59, microsecond=999999)
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def start_of_month(dt: datetime) -> datetime:
|
|
120
|
+
"""Get start of month."""
|
|
121
|
+
return dt.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def sleep(seconds: float) -> None:
|
|
125
|
+
"""Sleep for specified seconds."""
|
|
126
|
+
time.sleep(seconds)
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def weekday_name(dt: datetime) -> str:
|
|
130
|
+
"""Get weekday name."""
|
|
131
|
+
days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
|
132
|
+
return days[dt.weekday()]
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def month_name(dt: datetime) -> str:
|
|
136
|
+
"""Get month name."""
|
|
137
|
+
return dt.strftime('%B')
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Export functions for easy access
|
|
141
|
+
now = DateTimeModule.now
|
|
142
|
+
utc_now = DateTimeModule.utc_now
|
|
143
|
+
timestamp = DateTimeModule.timestamp
|
|
144
|
+
from_timestamp = DateTimeModule.from_timestamp
|
|
145
|
+
parse = DateTimeModule.parse
|
|
146
|
+
format_dt = DateTimeModule.format
|
|
147
|
+
iso_format = DateTimeModule.iso_format
|
|
148
|
+
to_dict = DateTimeModule.to_dict
|
|
149
|
+
add_days = DateTimeModule.add_days
|
|
150
|
+
add_hours = DateTimeModule.add_hours
|
|
151
|
+
add_minutes = DateTimeModule.add_minutes
|
|
152
|
+
add_seconds = DateTimeModule.add_seconds
|
|
153
|
+
diff_days = DateTimeModule.diff_days
|
|
154
|
+
diff_seconds = DateTimeModule.diff_seconds
|
|
155
|
+
is_before = DateTimeModule.is_before
|
|
156
|
+
is_after = DateTimeModule.is_after
|
|
157
|
+
is_between = DateTimeModule.is_between
|
|
158
|
+
start_of_day = DateTimeModule.start_of_day
|
|
159
|
+
end_of_day = DateTimeModule.end_of_day
|
|
160
|
+
start_of_month = DateTimeModule.start_of_month
|
|
161
|
+
sleep = DateTimeModule.sleep
|
|
162
|
+
weekday_name = DateTimeModule.weekday_name
|
|
163
|
+
month_name = DateTimeModule.month_name
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""MongoDB database driver for Zexus.
|
|
2
|
+
Requires pymongo: pip install pymongo
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from pymongo import MongoClient
|
|
7
|
+
from pymongo.errors import PyMongoError
|
|
8
|
+
from bson.objectid import ObjectId
|
|
9
|
+
MONGO_AVAILABLE = True
|
|
10
|
+
except ImportError:
|
|
11
|
+
MONGO_AVAILABLE = False
|
|
12
|
+
print("Warning: pymongo not installed. MongoDB support unavailable.")
|
|
13
|
+
print("Install with: pip install pymongo")
|
|
14
|
+
|
|
15
|
+
from typing import Any, List, Dict, Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MongoDBConnection:
|
|
19
|
+
"""MongoDB database connection."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, host: str = 'localhost', port: int = 27017,
|
|
22
|
+
database: str = 'test', username: Optional[str] = None,
|
|
23
|
+
password: Optional[str] = None):
|
|
24
|
+
"""Create MongoDB connection.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
host: Database host
|
|
28
|
+
port: Database port
|
|
29
|
+
database: Database name
|
|
30
|
+
username: Optional username
|
|
31
|
+
password: Optional password
|
|
32
|
+
"""
|
|
33
|
+
self.host = host
|
|
34
|
+
self.port = port
|
|
35
|
+
self.database_name = database
|
|
36
|
+
self.username = username
|
|
37
|
+
self.password = password
|
|
38
|
+
self.client = None
|
|
39
|
+
self.db = None
|
|
40
|
+
|
|
41
|
+
def connect(self) -> bool:
|
|
42
|
+
"""Open connection to database."""
|
|
43
|
+
if not MONGO_AVAILABLE:
|
|
44
|
+
print("MongoDB driver not available (pymongo not installed)")
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
if self.username and self.password:
|
|
49
|
+
uri = f"mongodb://{self.username}:{self.password}@{self.host}:{self.port}/{self.database_name}"
|
|
50
|
+
else:
|
|
51
|
+
uri = f"mongodb://{self.host}:{self.port}/"
|
|
52
|
+
|
|
53
|
+
self.client = MongoClient(uri, serverSelectionTimeoutMS=5000)
|
|
54
|
+
# Test connection
|
|
55
|
+
self.client.server_info()
|
|
56
|
+
self.db = self.client[self.database_name]
|
|
57
|
+
return True
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"MongoDB connect error: {e}")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
def collection(self, name: str):
|
|
63
|
+
"""Get a collection."""
|
|
64
|
+
if self.db is None:
|
|
65
|
+
print("MongoDB error: Not connected to database")
|
|
66
|
+
return None
|
|
67
|
+
return self.db[name]
|
|
68
|
+
|
|
69
|
+
def insert_one(self, collection: str, document: Dict[str, Any]) -> Optional[str]:
|
|
70
|
+
"""Insert a single document."""
|
|
71
|
+
try:
|
|
72
|
+
coll = self.collection(collection)
|
|
73
|
+
if coll is None:
|
|
74
|
+
return None
|
|
75
|
+
result = coll.insert_one(document)
|
|
76
|
+
return str(result.inserted_id)
|
|
77
|
+
except PyMongoError as e:
|
|
78
|
+
print(f"MongoDB insert_one error: {e}")
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
def insert_many(self, collection: str, documents: List[Dict[str, Any]]) -> Optional[List[str]]:
|
|
82
|
+
"""Insert multiple documents."""
|
|
83
|
+
try:
|
|
84
|
+
coll = self.collection(collection)
|
|
85
|
+
if coll is None:
|
|
86
|
+
return None
|
|
87
|
+
result = coll.insert_many(documents)
|
|
88
|
+
return [str(id) for id in result.inserted_ids]
|
|
89
|
+
except PyMongoError as e:
|
|
90
|
+
print(f"MongoDB insert_many error: {e}")
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
def find(self, collection: str, query: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
|
|
94
|
+
"""Find documents matching query."""
|
|
95
|
+
try:
|
|
96
|
+
coll = self.collection(collection)
|
|
97
|
+
if coll is None:
|
|
98
|
+
return []
|
|
99
|
+
|
|
100
|
+
query = query or {}
|
|
101
|
+
results = list(coll.find(query))
|
|
102
|
+
|
|
103
|
+
# Convert ObjectId to string for Zexus
|
|
104
|
+
for doc in results:
|
|
105
|
+
if '_id' in doc and isinstance(doc['_id'], ObjectId):
|
|
106
|
+
doc['_id'] = str(doc['_id'])
|
|
107
|
+
|
|
108
|
+
return results
|
|
109
|
+
except PyMongoError as e:
|
|
110
|
+
print(f"MongoDB find error: {e}")
|
|
111
|
+
return []
|
|
112
|
+
|
|
113
|
+
def find_one(self, collection: str, query: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
|
|
114
|
+
"""Find a single document matching query."""
|
|
115
|
+
try:
|
|
116
|
+
coll = self.collection(collection)
|
|
117
|
+
if coll is None:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
query = query or {}
|
|
121
|
+
doc = coll.find_one(query)
|
|
122
|
+
|
|
123
|
+
if doc and '_id' in doc and isinstance(doc['_id'], ObjectId):
|
|
124
|
+
doc['_id'] = str(doc['_id'])
|
|
125
|
+
|
|
126
|
+
return doc
|
|
127
|
+
except PyMongoError as e:
|
|
128
|
+
print(f"MongoDB find_one error: {e}")
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
def update_one(self, collection: str, query: Dict[str, Any], update: Dict[str, Any]) -> int:
|
|
132
|
+
"""Update a single document."""
|
|
133
|
+
try:
|
|
134
|
+
coll = self.collection(collection)
|
|
135
|
+
if coll is None:
|
|
136
|
+
return 0
|
|
137
|
+
result = coll.update_one(query, update)
|
|
138
|
+
return result.modified_count
|
|
139
|
+
except PyMongoError as e:
|
|
140
|
+
print(f"MongoDB update_one error: {e}")
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
def update_many(self, collection: str, query: Dict[str, Any], update: Dict[str, Any]) -> int:
|
|
144
|
+
"""Update multiple documents."""
|
|
145
|
+
try:
|
|
146
|
+
coll = self.collection(collection)
|
|
147
|
+
if coll is None:
|
|
148
|
+
return 0
|
|
149
|
+
result = coll.update_many(query, update)
|
|
150
|
+
return result.modified_count
|
|
151
|
+
except PyMongoError as e:
|
|
152
|
+
print(f"MongoDB update_many error: {e}")
|
|
153
|
+
return 0
|
|
154
|
+
|
|
155
|
+
def delete_one(self, collection: str, query: Dict[str, Any]) -> int:
|
|
156
|
+
"""Delete a single document."""
|
|
157
|
+
try:
|
|
158
|
+
coll = self.collection(collection)
|
|
159
|
+
if coll is None:
|
|
160
|
+
return 0
|
|
161
|
+
result = coll.delete_one(query)
|
|
162
|
+
return result.deleted_count
|
|
163
|
+
except PyMongoError as e:
|
|
164
|
+
print(f"MongoDB delete_one error: {e}")
|
|
165
|
+
return 0
|
|
166
|
+
|
|
167
|
+
def delete_many(self, collection: str, query: Dict[str, Any]) -> int:
|
|
168
|
+
"""Delete multiple documents."""
|
|
169
|
+
try:
|
|
170
|
+
coll = self.collection(collection)
|
|
171
|
+
if coll is None:
|
|
172
|
+
return 0
|
|
173
|
+
result = coll.delete_many(query)
|
|
174
|
+
return result.deleted_count
|
|
175
|
+
except PyMongoError as e:
|
|
176
|
+
print(f"MongoDB delete_many error: {e}")
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
def count(self, collection: str, query: Optional[Dict[str, Any]] = None) -> int:
|
|
180
|
+
"""Count documents matching query."""
|
|
181
|
+
try:
|
|
182
|
+
coll = self.collection(collection)
|
|
183
|
+
if coll is None:
|
|
184
|
+
return 0
|
|
185
|
+
query = query or {}
|
|
186
|
+
return coll.count_documents(query)
|
|
187
|
+
except PyMongoError as e:
|
|
188
|
+
print(f"MongoDB count error: {e}")
|
|
189
|
+
return 0
|
|
190
|
+
|
|
191
|
+
def close(self) -> bool:
|
|
192
|
+
"""Close database connection."""
|
|
193
|
+
try:
|
|
194
|
+
if self.client:
|
|
195
|
+
self.client.close()
|
|
196
|
+
return True
|
|
197
|
+
except Exception as e:
|
|
198
|
+
print(f"MongoDB close error: {e}")
|
|
199
|
+
return False
|