python-template-project 0.1.0__py3-none-any.whl → 0.1.18__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.
- __init__.py +0 -0
- firewall_handler.py +198 -0
- main.py +153 -0
- python_template_project/_version.py +21 -0
- python_template_project/core/base.py +401 -249
- python_template_project-0.1.18.dist-info/METADATA +86 -0
- {python_template_project-0.1.0.dist-info → python_template_project-0.1.18.dist-info}/RECORD +11 -6
- {python_template_project-0.1.0.dist-info → python_template_project-0.1.18.dist-info}/WHEEL +2 -1
- python_template_project-0.1.18.dist-info/top_level.txt +4 -0
- python_template_project-0.1.0.dist-info/METADATA +0 -92
- {python_template_project-0.1.0.dist-info → python_template_project-0.1.18.dist-info}/entry_points.txt +0 -0
- {python_template_project-0.1.0.dist-info → python_template_project-0.1.18.dist-info}/licenses/LICENSE +0 -0
__init__.py
ADDED
|
File without changes
|
firewall_handler.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import socket
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import traceback
|
|
6
|
+
import urllib.error
|
|
7
|
+
import urllib.request
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FirewallHandler:
|
|
12
|
+
"""Handles firewall detection and configuration for network access."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, logger=None):
|
|
15
|
+
self.logger = logger
|
|
16
|
+
self.test_urls = [
|
|
17
|
+
"http://httpbin.org/get",
|
|
18
|
+
"http://www.google.com",
|
|
19
|
+
"https://httpbin.org/get",
|
|
20
|
+
"https://www.google.com",
|
|
21
|
+
]
|
|
22
|
+
self.srtm_urls = [
|
|
23
|
+
"https://cloud.sdsc.edu/v1/AUTH_opentopography/Raster/SRTM_GL1/",
|
|
24
|
+
"http://cloud.sdsc.edu/v1/AUTH_opentopography/Raster/SRTM_GL1/",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
def check_network_access(self, timeout: int = 5) -> bool:
|
|
28
|
+
"""Check if network access is available."""
|
|
29
|
+
self.logger.info("Checking network connectivity...")
|
|
30
|
+
|
|
31
|
+
# First check basic connectivity
|
|
32
|
+
if not self._check_basic_connectivity():
|
|
33
|
+
self.logger.warning("Basic network connectivity failed")
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
# Then check HTTP access
|
|
37
|
+
if not self._check_http_access(timeout):
|
|
38
|
+
self.logger.warning("HTTP access failed")
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
# Finally check SRTM-specific URLs
|
|
42
|
+
if not self._check_srtm_access(timeout):
|
|
43
|
+
self.logger.warning("SRTM service access failed")
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
self.logger.info("Network access verified successfully")
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
def _check_basic_connectivity(self) -> bool:
|
|
50
|
+
"""Check basic network connectivity using socket."""
|
|
51
|
+
try:
|
|
52
|
+
# Try to connect to DNS servers
|
|
53
|
+
for host, port in [("8.8.8.8", 53), ("1.1.1.1", 53)]:
|
|
54
|
+
try:
|
|
55
|
+
with socket.create_connection((host, port), timeout=3):
|
|
56
|
+
return True
|
|
57
|
+
except (TimeoutError, OSError):
|
|
58
|
+
continue
|
|
59
|
+
return False
|
|
60
|
+
except Exception as e:
|
|
61
|
+
self.logger.debug(f"Basic connectivity check failed: {e}")
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
def _check_http_access(self, timeout: int) -> bool:
|
|
65
|
+
"""Check HTTP access to common websites."""
|
|
66
|
+
for url in self.test_urls:
|
|
67
|
+
try:
|
|
68
|
+
with urllib.request.urlopen(url, timeout=timeout) as response:
|
|
69
|
+
if response.status == 200:
|
|
70
|
+
self.logger.debug(f"HTTP access confirmed via {url}")
|
|
71
|
+
return True
|
|
72
|
+
except (TimeoutError, urllib.error.URLError, urllib.error.HTTPError) as e:
|
|
73
|
+
self.logger.debug(f"HTTP access failed for {url}: {e}")
|
|
74
|
+
continue
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
def _check_srtm_access(self, timeout: int) -> bool:
|
|
78
|
+
"""Check access to SRTM-specific URLs."""
|
|
79
|
+
for url in self.srtm_urls:
|
|
80
|
+
try:
|
|
81
|
+
with urllib.request.urlopen(url, timeout=timeout) as response:
|
|
82
|
+
if response.status in [200, 403]: # 403 is expected for directory listing
|
|
83
|
+
self.logger.debug(f"SRTM access confirmed via {url}")
|
|
84
|
+
return True
|
|
85
|
+
except (TimeoutError, urllib.error.URLError, urllib.error.HTTPError) as e:
|
|
86
|
+
self.logger.debug(f"SRTM access failed for {url}: {e}")
|
|
87
|
+
continue
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
def handle_firewall_issue(self):
|
|
91
|
+
"""Handle firewall issues by attempting to create rules and inform user."""
|
|
92
|
+
self.logger.info("Attempting to resolve firewall issues...")
|
|
93
|
+
|
|
94
|
+
# Get current executable path
|
|
95
|
+
exe_path = self._get_executable_path()
|
|
96
|
+
if not exe_path:
|
|
97
|
+
self.logger.error("Could not determine executable path")
|
|
98
|
+
self._show_manual_instructions()
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
# Try to create firewall rules
|
|
102
|
+
if self._is_windows():
|
|
103
|
+
self._handle_windows_firewall(exe_path)
|
|
104
|
+
else:
|
|
105
|
+
self._handle_linux_firewall(exe_path)
|
|
106
|
+
|
|
107
|
+
def _get_executable_path(self) -> str | None:
|
|
108
|
+
"""Get the path of the current executable."""
|
|
109
|
+
try:
|
|
110
|
+
if getattr(sys, "frozen", False):
|
|
111
|
+
# PyInstaller executable
|
|
112
|
+
return sys.executable
|
|
113
|
+
else:
|
|
114
|
+
# Python script
|
|
115
|
+
return sys.executable
|
|
116
|
+
except Exception as e:
|
|
117
|
+
self.logger.error(f"Error getting executable path: {e}")
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
def _is_windows(self) -> bool:
|
|
121
|
+
"""Check if running on Windows."""
|
|
122
|
+
return os.name == "nt"
|
|
123
|
+
|
|
124
|
+
def _handle_windows_firewall(self, exe_path: str):
|
|
125
|
+
"""Handle Windows firewall configuration."""
|
|
126
|
+
self.logger.info("Attempting to configure Windows Firewall...")
|
|
127
|
+
|
|
128
|
+
app_name = Path(exe_path).stem
|
|
129
|
+
|
|
130
|
+
# Commands to add firewall rules
|
|
131
|
+
commands = [
|
|
132
|
+
# Inbound rule
|
|
133
|
+
[
|
|
134
|
+
"netsh",
|
|
135
|
+
"advfirewall",
|
|
136
|
+
"firewall",
|
|
137
|
+
"add",
|
|
138
|
+
"rule",
|
|
139
|
+
f"name={app_name}_Inbound",
|
|
140
|
+
"dir=in",
|
|
141
|
+
"action=allow",
|
|
142
|
+
f"program={exe_path}",
|
|
143
|
+
"enable=yes",
|
|
144
|
+
],
|
|
145
|
+
# Outbound rule
|
|
146
|
+
[
|
|
147
|
+
"netsh",
|
|
148
|
+
"advfirewall",
|
|
149
|
+
"firewall",
|
|
150
|
+
"add",
|
|
151
|
+
"rule",
|
|
152
|
+
f"name={app_name}_Outbound",
|
|
153
|
+
"dir=out",
|
|
154
|
+
"action=allow",
|
|
155
|
+
f"program={exe_path}",
|
|
156
|
+
"enable=yes",
|
|
157
|
+
],
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
success = False
|
|
161
|
+
for cmd in commands:
|
|
162
|
+
try:
|
|
163
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
164
|
+
self.logger.info(
|
|
165
|
+
f"Firewall rule added successfully: {' '.join(cmd)} result: {result}"
|
|
166
|
+
)
|
|
167
|
+
success = True
|
|
168
|
+
except subprocess.CalledProcessError as e:
|
|
169
|
+
self.logger.warning(f"Failed to add firewall rule: {e}")
|
|
170
|
+
self.logger.debug(f"Command: {' '.join(cmd)}")
|
|
171
|
+
self.logger.debug(f"Error output: {e.stderr}")
|
|
172
|
+
except Exception as e:
|
|
173
|
+
self.logger.error(f"Unexpected error adding firewall rule: {e}")
|
|
174
|
+
self.logger.debug(f"Full traceback:\n{traceback.format_exc()}")
|
|
175
|
+
|
|
176
|
+
if success:
|
|
177
|
+
self.logger.info(
|
|
178
|
+
"Windows Firewall rules added successfully. Please restart the application."
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
self.logger.warning("Failed to automatically configure Windows Firewall.")
|
|
182
|
+
self._show_manual_instructions()
|
|
183
|
+
|
|
184
|
+
def _handle_linux_firewall(self, exe_path: str):
|
|
185
|
+
"""Handle Linux firewall configuration."""
|
|
186
|
+
self.logger.info("Detected Linux system. Firewall configuration may be needed.")
|
|
187
|
+
self._show_manual_instructions()
|
|
188
|
+
|
|
189
|
+
def _show_manual_instructions(self):
|
|
190
|
+
"""Show general manual instructions"""
|
|
191
|
+
self.logger.info(
|
|
192
|
+
"Please check your firewall settings and ensure the application has network access.\n"
|
|
193
|
+
"Refer to the documentation for manual configuration steps."
|
|
194
|
+
"quick solution:"
|
|
195
|
+
"Start your EXE as administrator:"
|
|
196
|
+
"Right-click on EXE → Run as administrator"
|
|
197
|
+
"Or temporarily deactivate the firewall:"
|
|
198
|
+
)
|
main.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Unified entry point for CLI and GUI application.
|
|
4
|
+
Automatically detects whether to run CLI or GUI based on how the application is started.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_console_attached():
|
|
12
|
+
"""
|
|
13
|
+
Check if the application is running in a console environment.
|
|
14
|
+
Returns True if launched from console, False if launched via double-click.
|
|
15
|
+
"""
|
|
16
|
+
try:
|
|
17
|
+
# On Windows, check if we have a console attached
|
|
18
|
+
if os.name == "nt":
|
|
19
|
+
import ctypes
|
|
20
|
+
|
|
21
|
+
kernel32 = ctypes.windll.kernel32
|
|
22
|
+
# GetConsoleWindow returns 0 if no console is attached
|
|
23
|
+
console_window = kernel32.GetConsoleWindow()
|
|
24
|
+
if console_window == 0:
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
# Check if the console was created by this process
|
|
28
|
+
# (indicates double-click launch with console auto-created)
|
|
29
|
+
process_list = kernel32.GetConsoleProcessList(None, 0)
|
|
30
|
+
if process_list <= 1:
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
return True
|
|
34
|
+
else:
|
|
35
|
+
# On Unix-like systems (macOS, Linux), check multiple indicators
|
|
36
|
+
# Check if stdout is a terminal
|
|
37
|
+
if not sys.stdout.isatty():
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
# Additional check for macOS: Check if we're running in a .app bundle
|
|
41
|
+
if sys.platform == "darwin":
|
|
42
|
+
# If we're in a .app bundle, we're likely launched via double-click
|
|
43
|
+
executable_path = sys.executable
|
|
44
|
+
if ".app/" in executable_path:
|
|
45
|
+
# We're in an app bundle, check if we have a real terminal
|
|
46
|
+
# by checking if TERM environment variable is set
|
|
47
|
+
return os.environ.get("TERM") is not None
|
|
48
|
+
|
|
49
|
+
return True
|
|
50
|
+
except Exception as ex:
|
|
51
|
+
# Fallback: assume GUI if we can't determine
|
|
52
|
+
print(ex)
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def has_command_line_args():
|
|
57
|
+
"""Check if command line arguments (beyond script name) are provided."""
|
|
58
|
+
return len(sys.argv) > 1
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def main():
|
|
62
|
+
"""Main entry point that decides between CLI and GUI."""
|
|
63
|
+
|
|
64
|
+
# Force CLI mode if command line arguments are provided
|
|
65
|
+
if has_command_line_args():
|
|
66
|
+
run_cli()
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
# Check if we're in a console environment
|
|
70
|
+
if is_console_attached():
|
|
71
|
+
# We're in a console - offer choice or default to CLI
|
|
72
|
+
print("Python Template Project")
|
|
73
|
+
print("=" * 50)
|
|
74
|
+
print("Detected console environment.")
|
|
75
|
+
print("Options:")
|
|
76
|
+
print(" 1. Run CLI interface (default)")
|
|
77
|
+
print(" 2. Run GUI interface")
|
|
78
|
+
print(" 3. Show help")
|
|
79
|
+
print()
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
choice = input("Select option [1]: ").strip()
|
|
83
|
+
if choice == "2":
|
|
84
|
+
run_gui()
|
|
85
|
+
elif choice == "3":
|
|
86
|
+
show_help()
|
|
87
|
+
else:
|
|
88
|
+
run_cli()
|
|
89
|
+
except (KeyboardInterrupt, EOFError):
|
|
90
|
+
print("\nExiting...")
|
|
91
|
+
sys.exit(0)
|
|
92
|
+
else:
|
|
93
|
+
# Launched via double-click - start GUI
|
|
94
|
+
run_gui()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def run_cli():
|
|
98
|
+
"""Launch the CLI interface."""
|
|
99
|
+
try:
|
|
100
|
+
from python_template_project.cli.cli import main as cli_main
|
|
101
|
+
|
|
102
|
+
cli_main()
|
|
103
|
+
except ImportError as e:
|
|
104
|
+
print(f"Error importing CLI module: {e}")
|
|
105
|
+
sys.exit(1)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
print(f"Error running CLI: {e}")
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def run_gui():
|
|
112
|
+
"""Launch the GUI interface."""
|
|
113
|
+
try:
|
|
114
|
+
from python_template_project.gui.gui import main as gui_main
|
|
115
|
+
|
|
116
|
+
gui_main()
|
|
117
|
+
except ImportError as e:
|
|
118
|
+
print(f"Error importing GUI module: {e}")
|
|
119
|
+
# If GUI fails, fallback to CLI
|
|
120
|
+
print("Falling back to CLI interface...")
|
|
121
|
+
run_cli()
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(f"Error running GUI: {e}")
|
|
124
|
+
print("Falling back to CLI interface...")
|
|
125
|
+
run_cli()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def show_help():
|
|
129
|
+
"""Show help information."""
|
|
130
|
+
print("""
|
|
131
|
+
Unified Application
|
|
132
|
+
|
|
133
|
+
Usage:
|
|
134
|
+
When launched from console:
|
|
135
|
+
- Without arguments: Interactive mode selection
|
|
136
|
+
- With arguments: Direct CLI mode
|
|
137
|
+
|
|
138
|
+
When launched via double-click:
|
|
139
|
+
- Automatically starts GUI interface
|
|
140
|
+
|
|
141
|
+
Command Line Arguments:
|
|
142
|
+
Run with any CLI arguments to force CLI mode.
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
python main.py --help # Shows CLI help
|
|
146
|
+
python main.py # Interactive mode selection
|
|
147
|
+
double-click main.exe # Starts GUI
|
|
148
|
+
|
|
149
|
+
""")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '0.1.18'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 1, 18)
|