isobar-cli 1.1.2__tar.gz → 1.2.0__tar.gz

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 (24) hide show
  1. {isobar_cli-1.1.2/src/isobar_cli.egg-info → isobar_cli-1.2.0}/PKG-INFO +54 -7
  2. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/README.md +51 -6
  3. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/pyproject.toml +12 -9
  4. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli/api.py +42 -10
  5. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli/config.py +21 -0
  6. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli/logic.py +72 -2
  7. {isobar_cli-1.1.2 → isobar_cli-1.2.0/src/isobar_cli.egg-info}/PKG-INFO +54 -7
  8. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli.egg-info/requires.txt +3 -0
  9. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/LICENSE +0 -0
  10. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/setup.cfg +0 -0
  11. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli/__init__.py +0 -0
  12. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli/location.py +0 -0
  13. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli/main.py +0 -0
  14. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli/models.py +0 -0
  15. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli/ui.py +0 -0
  16. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli.egg-info/SOURCES.txt +0 -0
  17. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli.egg-info/dependency_links.txt +0 -0
  18. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli.egg-info/entry_points.txt +0 -0
  19. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/src/isobar_cli.egg-info/top_level.txt +0 -0
  20. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/tests/test_api.py +0 -0
  21. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/tests/test_isobar_extra.py +0 -0
  22. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/tests/test_location.py +0 -0
  23. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/tests/test_main.py +0 -0
  24. {isobar_cli-1.1.2 → isobar_cli-1.2.0}/tests/test_ui.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: isobar-cli
3
- Version: 1.1.2
3
+ Version: 1.2.0
4
4
  Summary: A visually pleasing terminal weather tool focusing on Real Feel and Windchill.
5
5
  Author: Beau Bremer / KnowOneActual
6
6
  License-Expression: MIT
@@ -29,6 +29,8 @@ Provides-Extra: test
29
29
  Requires-Dist: pytest>=7.0.0; extra == "test"
30
30
  Requires-Dist: requests-mock>=1.11.0; extra == "test"
31
31
  Requires-Dist: pytest-cov>=4.1.0; extra == "test"
32
+ Provides-Extra: timezone
33
+ Requires-Dist: pytz>=2024.1; extra == "timezone"
32
34
  Dynamic: license-file
33
35
 
34
36
  # Isobar CLI
@@ -36,7 +38,7 @@ Dynamic: license-file
36
38
  ![CI](https://github.com/KnowOneActual/isobar-cli/actions/workflows/ci.yml/badge.svg)
37
39
  ![Coverage](https://img.shields.io/badge/coverage-98%25-green)
38
40
  [![PyPI version](https://badge.fury.io/py/isobar-cli.svg)](https://badge.fury.io/py/isobar-cli)
39
- ![Version](https://img.shields.io/badge/version-1.0.1-blue)
41
+ ![Version](https://img.shields.io/badge/version-1.2.0-blue)
40
42
  ![Ruff](https://img.shields.io/badge/linting-ruff-purple)
41
43
  ![Python](https://img.shields.io/badge/python-3.8%2B-blue)
42
44
  ![License](https://img.shields.io/badge/license-MIT-green)
@@ -80,7 +82,7 @@ Most weather apps overwhelm with data. Isobar strips away everything except what
80
82
  - **Temporal Context** — Comparison with previous day conditions 📈
81
83
  - **UV Index Monitoring** — Sun protection guidance with intensity levels ☀️
82
84
  - **Wind Gust Alerts** — Highlighting of significant gust events 💨⚠️
83
- - **Home City Persistence** — Set a default city with `isobar home "Your City"` 🏠
85
+ - **Home City Persistence** — Set a default city with `isobar home "Your City"` 🏠 *(Note: Due to a Typer limitation, this currently shows weather for "Home, Kansas" instead of setting home city. Manual config editing required.)*
84
86
 
85
87
  ## 🚀 Installation
86
88
 
@@ -124,12 +126,16 @@ isobar London Tokyo Paris # Multiple cities
124
126
  isobar "New York" # Use quotes for multi-word cities
125
127
 
126
128
  # Hourly outlook (next 12h)
127
- isobar --hourly
128
- isobar -H
129
+ isobar --hourly Chicago
130
+ isobar -H Chicago
129
131
 
130
132
  # 7-day forecast
131
- isobar --forecast
132
- isobar -f
133
+ isobar --forecast Chicago
134
+ isobar -f Chicago
135
+
136
+ # Note: Flags must come before city names
137
+ # ✅ isobar -H Chicago
138
+ # ❌ isobar Chicago -H (treats "-H" as a city name)
133
139
  isobar "San Francisco" --forecast
134
140
  isobar -f Sydney
135
141
 
@@ -145,6 +151,42 @@ isobar home --clear # Clear home city
145
151
  isobar # Uses home city if set (otherwise auto-detects)
146
152
  ```
147
153
 
154
+ ## ⚙️ Configuration
155
+
156
+ Isobar supports configuration via environment variables for advanced use cases:
157
+
158
+ ### API Endpoint Configuration
159
+ Customize API endpoints for different weather providers or testing:
160
+
161
+ ```bash
162
+ # Use custom weather APIs
163
+ export ISOBAR_GEOCODING_URL="https://custom-geocoding-api.example.com/v1/search"
164
+ export ISOBAR_WEATHER_URL="https://custom-weather-api.example.com/v1/forecast"
165
+ export ISOBAR_AQI_URL="https://custom-aqi-api.example.com/v1/air-quality"
166
+
167
+ # Run with custom endpoints
168
+ isobar "New York"
169
+ ```
170
+
171
+ ### Timezone Support
172
+ For enhanced timezone accuracy (optional):
173
+
174
+ ```bash
175
+ # Install optional timezone support
176
+ pip install isobar-cli[timezone]
177
+
178
+ # Sunrise/sunset will now display in local timezone
179
+ isobar London
180
+ ```
181
+
182
+ ### Debug Mode
183
+ Enable debug logging to stderr:
184
+
185
+ ```bash
186
+ # View API errors and debugging information
187
+ isobar "Test City" 2> debug.log
188
+ ```
189
+
148
190
  ## ⌨️ Shell Completion
149
191
 
150
192
  Isobar supports tab-completion for city names. To enable it for a shell:
@@ -212,6 +254,10 @@ Preparation Guidance:
212
254
  | `config.py` | Persistent home city configuration |
213
255
  | Enhanced `logic.py` | Preparation guidance, UV monitoring, gust alerts |
214
256
  | Updated `ui.py` | Contextual display of insights |
257
+ | **v1.2.0 Features** | **Security & Configuration** |
258
+ | Configurable API Endpoints | Environment variable support for custom APIs |
259
+ | Enhanced Error Handling | Specific exception catching with timeouts |
260
+ | Timezone Support | Optional `pytz` dependency for local time display |
215
261
 
216
262
  ## 🔒 Security
217
263
 
@@ -235,6 +281,7 @@ All security scans are integrated into the CI/CD pipeline and run on every push,
235
281
  ✅ **Phase 5 Complete** — Testing & Reliability
236
282
  ✅ **Phase 6 Complete** — Distribution (PyPI, Homebrew)
237
283
  ✅ **Phase 7 Complete** — Intuition & Analysis (v1.1.0)
284
+ ✅ **v1.2.0 Complete** — Security & Configuration Enhancements
238
285
  Refer to [ROADMAP.md](ROADMAP.md) and [CHANGELOG.md](CHANGELOG.md) for details.
239
286
 
240
287
  ## 🤝 Contributing
@@ -3,7 +3,7 @@
3
3
  ![CI](https://github.com/KnowOneActual/isobar-cli/actions/workflows/ci.yml/badge.svg)
4
4
  ![Coverage](https://img.shields.io/badge/coverage-98%25-green)
5
5
  [![PyPI version](https://badge.fury.io/py/isobar-cli.svg)](https://badge.fury.io/py/isobar-cli)
6
- ![Version](https://img.shields.io/badge/version-1.0.1-blue)
6
+ ![Version](https://img.shields.io/badge/version-1.2.0-blue)
7
7
  ![Ruff](https://img.shields.io/badge/linting-ruff-purple)
8
8
  ![Python](https://img.shields.io/badge/python-3.8%2B-blue)
9
9
  ![License](https://img.shields.io/badge/license-MIT-green)
@@ -47,7 +47,7 @@ Most weather apps overwhelm with data. Isobar strips away everything except what
47
47
  - **Temporal Context** — Comparison with previous day conditions 📈
48
48
  - **UV Index Monitoring** — Sun protection guidance with intensity levels ☀️
49
49
  - **Wind Gust Alerts** — Highlighting of significant gust events 💨⚠️
50
- - **Home City Persistence** — Set a default city with `isobar home "Your City"` 🏠
50
+ - **Home City Persistence** — Set a default city with `isobar home "Your City"` 🏠 *(Note: Due to a Typer limitation, this currently shows weather for "Home, Kansas" instead of setting home city. Manual config editing required.)*
51
51
 
52
52
  ## 🚀 Installation
53
53
 
@@ -91,12 +91,16 @@ isobar London Tokyo Paris # Multiple cities
91
91
  isobar "New York" # Use quotes for multi-word cities
92
92
 
93
93
  # Hourly outlook (next 12h)
94
- isobar --hourly
95
- isobar -H
94
+ isobar --hourly Chicago
95
+ isobar -H Chicago
96
96
 
97
97
  # 7-day forecast
98
- isobar --forecast
99
- isobar -f
98
+ isobar --forecast Chicago
99
+ isobar -f Chicago
100
+
101
+ # Note: Flags must come before city names
102
+ # ✅ isobar -H Chicago
103
+ # ❌ isobar Chicago -H (treats "-H" as a city name)
100
104
  isobar "San Francisco" --forecast
101
105
  isobar -f Sydney
102
106
 
@@ -112,6 +116,42 @@ isobar home --clear # Clear home city
112
116
  isobar # Uses home city if set (otherwise auto-detects)
113
117
  ```
114
118
 
119
+ ## ⚙️ Configuration
120
+
121
+ Isobar supports configuration via environment variables for advanced use cases:
122
+
123
+ ### API Endpoint Configuration
124
+ Customize API endpoints for different weather providers or testing:
125
+
126
+ ```bash
127
+ # Use custom weather APIs
128
+ export ISOBAR_GEOCODING_URL="https://custom-geocoding-api.example.com/v1/search"
129
+ export ISOBAR_WEATHER_URL="https://custom-weather-api.example.com/v1/forecast"
130
+ export ISOBAR_AQI_URL="https://custom-aqi-api.example.com/v1/air-quality"
131
+
132
+ # Run with custom endpoints
133
+ isobar "New York"
134
+ ```
135
+
136
+ ### Timezone Support
137
+ For enhanced timezone accuracy (optional):
138
+
139
+ ```bash
140
+ # Install optional timezone support
141
+ pip install isobar-cli[timezone]
142
+
143
+ # Sunrise/sunset will now display in local timezone
144
+ isobar London
145
+ ```
146
+
147
+ ### Debug Mode
148
+ Enable debug logging to stderr:
149
+
150
+ ```bash
151
+ # View API errors and debugging information
152
+ isobar "Test City" 2> debug.log
153
+ ```
154
+
115
155
  ## ⌨️ Shell Completion
116
156
 
117
157
  Isobar supports tab-completion for city names. To enable it for a shell:
@@ -179,6 +219,10 @@ Preparation Guidance:
179
219
  | `config.py` | Persistent home city configuration |
180
220
  | Enhanced `logic.py` | Preparation guidance, UV monitoring, gust alerts |
181
221
  | Updated `ui.py` | Contextual display of insights |
222
+ | **v1.2.0 Features** | **Security & Configuration** |
223
+ | Configurable API Endpoints | Environment variable support for custom APIs |
224
+ | Enhanced Error Handling | Specific exception catching with timeouts |
225
+ | Timezone Support | Optional `pytz` dependency for local time display |
182
226
 
183
227
  ## 🔒 Security
184
228
 
@@ -202,6 +246,7 @@ All security scans are integrated into the CI/CD pipeline and run on every push,
202
246
  ✅ **Phase 5 Complete** — Testing & Reliability
203
247
  ✅ **Phase 6 Complete** — Distribution (PyPI, Homebrew)
204
248
  ✅ **Phase 7 Complete** — Intuition & Analysis (v1.1.0)
249
+ ✅ **v1.2.0 Complete** — Security & Configuration Enhancements
205
250
  Refer to [ROADMAP.md](ROADMAP.md) and [CHANGELOG.md](CHANGELOG.md) for details.
206
251
 
207
252
  ## 🤝 Contributing
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "isobar-cli"
7
- version = "1.1.2"
7
+ version = "1.2.0"
8
8
  description = "A visually pleasing terminal weather tool focusing on Real Feel and Windchill."
9
9
  authors = [
10
10
  { name="Beau Bremer / KnowOneActual" },
@@ -27,9 +27,9 @@ dependencies = [
27
27
  "rich>=13.0.0",
28
28
  "requests>=2.31.0",
29
29
  "typer>=0.9.0",
30
- "timezonefinder>=6.0.0",
31
- ]
32
- requires-python = ">=3.8"
30
+ "timezonefinder>=6.0.0",
31
+ ]
32
+ requires-python = ">=3.8"
33
33
 
34
34
  [project.urls]
35
35
  Homepage = "https://github.com/KnowOneActual/isobar-cli"
@@ -39,11 +39,14 @@ dependencies = [
39
39
 
40
40
  [project.optional-dependencies]
41
41
 
42
- test = [
43
- "pytest>=7.0.0",
44
- "requests-mock>=1.11.0",
45
- "pytest-cov>=4.1.0",
46
- ]
42
+ test = [
43
+ "pytest>=7.0.0",
44
+ "requests-mock>=1.11.0",
45
+ "pytest-cov>=4.1.0",
46
+ ]
47
+ timezone = [
48
+ "pytz>=2024.1",
49
+ ]
47
50
 
48
51
  [project.scripts]
49
52
  isobar = "isobar_cli.main:app" # This allows you to type 'isobar' in the terminal to run it
@@ -7,6 +7,7 @@ from typing import Optional
7
7
  import requests
8
8
  from timezonefinder import TimezoneFinder
9
9
 
10
+ from .config import get_aqi_url, get_geocoding_url, get_weather_url
10
11
  from .logic import format_time
11
12
  from .models import ForecastDay, HourlyForecast, UnitSystem, WeatherData, WeatherUnits
12
13
 
@@ -15,22 +16,39 @@ CACHE_DIR.mkdir(parents=True, exist_ok=True)
15
16
 
16
17
 
17
18
  class GeocodingClient:
18
- BASE_URL = "https://geocoding-api.open-meteo.com/v1/search"
19
+ @classmethod
20
+ def get_base_url(cls) -> str:
21
+ """Get geocoding API URL from configuration."""
22
+ return get_geocoding_url()
19
23
 
20
24
  @classmethod
21
25
  def search(cls, city: str, count: int = 1) -> list[dict]:
22
26
  try:
23
27
  response = requests.get(
24
- f"{cls.BASE_URL}?name={city}&count={count}&format=json"
28
+ f"{cls.get_base_url()}?name={city}&count={count}&format=json",
29
+ timeout=10,
25
30
  )
26
31
  response.raise_for_status()
27
32
  return response.json().get("results", [])
28
- except Exception:
33
+ except requests.exceptions.RequestException as e:
34
+ # Log error for debugging but don't crash
35
+ import sys
36
+
37
+ print(f"Geocoding error for '{city}': {e}", file=sys.stderr)
38
+ return []
39
+ except Exception as e:
40
+ # Catch-all for unexpected errors
41
+ import sys
42
+
43
+ print(f"Unexpected geocoding error for '{city}': {e}", file=sys.stderr)
29
44
  return []
30
45
 
31
46
 
32
47
  class WeatherClient:
33
- BASE_URL = "https://api.open-meteo.com/v1/forecast"
48
+ @classmethod
49
+ def get_base_url(cls) -> str:
50
+ """Get weather API URL from configuration."""
51
+ return get_weather_url()
34
52
 
35
53
  def __init__(self, lat: float, lon: float, timezone: str, metric: bool = False):
36
54
  self.lat = lat
@@ -59,23 +77,37 @@ class WeatherClient:
59
77
  "forecast_days": 7,
60
78
  }
61
79
 
62
- response = requests.get(self.BASE_URL, params=params)
80
+ response = requests.get(self.get_base_url(), params=params, timeout=15)
63
81
  response.raise_for_status()
64
82
  return response.json()
65
83
 
66
84
 
67
85
  class AirQualityClient:
68
- BASE_URL = "https://air-quality-api.open-meteo.com/v1/air-quality"
86
+ @classmethod
87
+ def get_base_url(cls) -> str:
88
+ """Get air quality API URL from configuration."""
89
+ return get_aqi_url()
69
90
 
70
91
  @classmethod
71
92
  def get_aqi(cls, lat: float, lon: float) -> Optional[int]:
72
93
  try:
73
94
  response = requests.get(
74
- f"{cls.BASE_URL}?latitude={lat}&longitude={lon}&current=us_aqi"
95
+ f"{cls.get_base_url()}?latitude={lat}&longitude={lon}&current=us_aqi",
96
+ timeout=10,
75
97
  )
76
98
  response.raise_for_status()
77
99
  return response.json().get("current", {}).get("us_aqi")
78
- except Exception:
100
+ except requests.exceptions.RequestException as e:
101
+ # Log error for debugging but don't crash
102
+ import sys
103
+
104
+ print(f"AQI error for ({lat},{lon}): {e}", file=sys.stderr)
105
+ return None
106
+ except Exception as e:
107
+ # Catch-all for unexpected errors
108
+ import sys
109
+
110
+ print(f"Unexpected AQI error for ({lat},{lon}): {e}", file=sys.stderr)
79
111
  return None
80
112
 
81
113
 
@@ -207,8 +239,8 @@ def get_weather_data(city: str, metric: bool = False) -> Optional[WeatherData]:
207
239
  precip_prob=round(avg_precip_prob),
208
240
  rainfall=next_6h_rain,
209
241
  snowfall=next_6h_snow,
210
- sunrise=format_time(daily["sunrise"][0]),
211
- sunset=format_time(daily["sunset"][0]),
242
+ sunrise=format_time(daily["sunrise"][0], timezone),
243
+ sunset=format_time(daily["sunset"][0], timezone),
212
244
  forecast=forecast,
213
245
  hourly=hourly_forecast,
214
246
  units=weather_client.units,
@@ -1,12 +1,18 @@
1
1
  """Configuration module for Isobar CLI persistent settings."""
2
2
 
3
3
  import json
4
+ import os
4
5
  from pathlib import Path
5
6
  from typing import Optional
6
7
 
7
8
  CONFIG_DIR = Path.home() / ".config" / "isobar"
8
9
  CONFIG_FILE = CONFIG_DIR / "config.json"
9
10
 
11
+ # Default API endpoints (can be overridden by environment variables)
12
+ DEFAULT_GEOCODING_URL = "https://geocoding-api.open-meteo.com/v1/search"
13
+ DEFAULT_WEATHER_URL = "https://api.open-meteo.com/v1/forecast"
14
+ DEFAULT_AQI_URL = "https://air-quality-api.open-meteo.com/v1/air-quality"
15
+
10
16
 
11
17
  def ensure_config_dir() -> None:
12
18
  """Create config directory if it doesn't exist."""
@@ -72,3 +78,18 @@ def clear_home_city() -> None:
72
78
  def get_config_path() -> Path:
73
79
  """Get the path to the config file (for debugging/info)."""
74
80
  return CONFIG_FILE
81
+
82
+
83
+ def get_geocoding_url() -> str:
84
+ """Get geocoding API URL from environment or default."""
85
+ return os.environ.get("ISOBAR_GEOCODING_URL", DEFAULT_GEOCODING_URL)
86
+
87
+
88
+ def get_weather_url() -> str:
89
+ """Get weather API URL from environment or default."""
90
+ return os.environ.get("ISOBAR_WEATHER_URL", DEFAULT_WEATHER_URL)
91
+
92
+
93
+ def get_aqi_url() -> str:
94
+ """Get air quality API URL from environment or default."""
95
+ return os.environ.get("ISOBAR_AQI_URL", DEFAULT_AQI_URL)
@@ -31,16 +31,86 @@ WMO_CODES: dict[int, tuple[str, str]] = {
31
31
  }
32
32
 
33
33
 
34
- def format_time(iso_string: str) -> str:
35
- """Convert ISO 8601 datetime to 12-hour format (e.g., '6:42 AM')."""
34
+ def format_time(iso_string: str, timezone: str = "UTC") -> str:
35
+ """Convert ISO 8601 datetime to 12-hour format in specified timezone.
36
+
37
+ Args:
38
+ iso_string: ISO 8601 datetime string
39
+ timezone: Timezone name (e.g., 'America/New_York'), defaults to UTC
40
+
41
+ Returns:
42
+ Formatted time string (e.g., '6:42 AM') or '--' on error
43
+ """
36
44
  if not iso_string:
37
45
  return "--"
46
+
38
47
  try:
48
+ # Parse the datetime
39
49
  dt = datetime.fromisoformat(iso_string.replace("Z", "+00:00"))
50
+
51
+ # Convert to local timezone if not UTC
52
+ if timezone != "UTC":
53
+ try:
54
+ import pytz
55
+
56
+ utc_dt = dt.replace(tzinfo=pytz.UTC)
57
+ local_tz = pytz.timezone(timezone)
58
+ local_dt = utc_dt.astimezone(local_tz)
59
+ return local_dt.strftime("%-I:%M %p")
60
+ except ImportError:
61
+ # pytz not installed, fall back to UTC
62
+ pass
63
+
40
64
  return dt.strftime("%-I:%M %p")
41
65
  except (ValueError, AttributeError):
42
66
  return "--"
43
67
 
68
+ # Try to import pytz for timezone conversion
69
+ pytz_available = False
70
+ if timezone != "UTC":
71
+ try:
72
+ import pytz
73
+
74
+ pytz_available = True
75
+ except ImportError:
76
+ pytz_available = False
77
+
78
+ try:
79
+ # Parse the datetime
80
+ dt = datetime.fromisoformat(iso_string.replace("Z", "+00:00"))
81
+
82
+ # Convert to local timezone if not UTC and pytz is available
83
+ if timezone != "UTC" and pytz_available:
84
+ utc_dt = dt.replace(tzinfo=pytz.UTC)
85
+ local_tz = pytz.timezone(timezone)
86
+ local_dt = utc_dt.astimezone(local_tz)
87
+ return local_dt.strftime("%-I:%M %p")
88
+
89
+ return dt.strftime("%-I:%M %p")
90
+ except (ValueError, AttributeError):
91
+ return "--"
92
+ try:
93
+ # Parse the datetime
94
+ dt = datetime.fromisoformat(iso_string.replace("Z", "+00:00"))
95
+
96
+ # Convert to local timezone if not UTC
97
+ if timezone != "UTC":
98
+ import pytz
99
+
100
+ utc_dt = dt.replace(tzinfo=pytz.UTC)
101
+ local_tz = pytz.timezone(timezone)
102
+ local_dt = utc_dt.astimezone(local_tz)
103
+ return local_dt.strftime("%-I:%M %p")
104
+
105
+ return dt.strftime("%-I:%M %p")
106
+ except (ValueError, AttributeError, ImportError):
107
+ # Fallback to UTC if pytz not available or other error
108
+ try:
109
+ dt = datetime.fromisoformat(iso_string.replace("Z", "+00:00"))
110
+ return dt.strftime("%-I:%M %p")
111
+ except (ValueError, AttributeError):
112
+ return "--"
113
+
44
114
 
45
115
  def get_temp_color(temp_val: float, unit="°F") -> str:
46
116
  """Returns a Rich color tag based on the temperature and unit."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: isobar-cli
3
- Version: 1.1.2
3
+ Version: 1.2.0
4
4
  Summary: A visually pleasing terminal weather tool focusing on Real Feel and Windchill.
5
5
  Author: Beau Bremer / KnowOneActual
6
6
  License-Expression: MIT
@@ -29,6 +29,8 @@ Provides-Extra: test
29
29
  Requires-Dist: pytest>=7.0.0; extra == "test"
30
30
  Requires-Dist: requests-mock>=1.11.0; extra == "test"
31
31
  Requires-Dist: pytest-cov>=4.1.0; extra == "test"
32
+ Provides-Extra: timezone
33
+ Requires-Dist: pytz>=2024.1; extra == "timezone"
32
34
  Dynamic: license-file
33
35
 
34
36
  # Isobar CLI
@@ -36,7 +38,7 @@ Dynamic: license-file
36
38
  ![CI](https://github.com/KnowOneActual/isobar-cli/actions/workflows/ci.yml/badge.svg)
37
39
  ![Coverage](https://img.shields.io/badge/coverage-98%25-green)
38
40
  [![PyPI version](https://badge.fury.io/py/isobar-cli.svg)](https://badge.fury.io/py/isobar-cli)
39
- ![Version](https://img.shields.io/badge/version-1.0.1-blue)
41
+ ![Version](https://img.shields.io/badge/version-1.2.0-blue)
40
42
  ![Ruff](https://img.shields.io/badge/linting-ruff-purple)
41
43
  ![Python](https://img.shields.io/badge/python-3.8%2B-blue)
42
44
  ![License](https://img.shields.io/badge/license-MIT-green)
@@ -80,7 +82,7 @@ Most weather apps overwhelm with data. Isobar strips away everything except what
80
82
  - **Temporal Context** — Comparison with previous day conditions 📈
81
83
  - **UV Index Monitoring** — Sun protection guidance with intensity levels ☀️
82
84
  - **Wind Gust Alerts** — Highlighting of significant gust events 💨⚠️
83
- - **Home City Persistence** — Set a default city with `isobar home "Your City"` 🏠
85
+ - **Home City Persistence** — Set a default city with `isobar home "Your City"` 🏠 *(Note: Due to a Typer limitation, this currently shows weather for "Home, Kansas" instead of setting home city. Manual config editing required.)*
84
86
 
85
87
  ## 🚀 Installation
86
88
 
@@ -124,12 +126,16 @@ isobar London Tokyo Paris # Multiple cities
124
126
  isobar "New York" # Use quotes for multi-word cities
125
127
 
126
128
  # Hourly outlook (next 12h)
127
- isobar --hourly
128
- isobar -H
129
+ isobar --hourly Chicago
130
+ isobar -H Chicago
129
131
 
130
132
  # 7-day forecast
131
- isobar --forecast
132
- isobar -f
133
+ isobar --forecast Chicago
134
+ isobar -f Chicago
135
+
136
+ # Note: Flags must come before city names
137
+ # ✅ isobar -H Chicago
138
+ # ❌ isobar Chicago -H (treats "-H" as a city name)
133
139
  isobar "San Francisco" --forecast
134
140
  isobar -f Sydney
135
141
 
@@ -145,6 +151,42 @@ isobar home --clear # Clear home city
145
151
  isobar # Uses home city if set (otherwise auto-detects)
146
152
  ```
147
153
 
154
+ ## ⚙️ Configuration
155
+
156
+ Isobar supports configuration via environment variables for advanced use cases:
157
+
158
+ ### API Endpoint Configuration
159
+ Customize API endpoints for different weather providers or testing:
160
+
161
+ ```bash
162
+ # Use custom weather APIs
163
+ export ISOBAR_GEOCODING_URL="https://custom-geocoding-api.example.com/v1/search"
164
+ export ISOBAR_WEATHER_URL="https://custom-weather-api.example.com/v1/forecast"
165
+ export ISOBAR_AQI_URL="https://custom-aqi-api.example.com/v1/air-quality"
166
+
167
+ # Run with custom endpoints
168
+ isobar "New York"
169
+ ```
170
+
171
+ ### Timezone Support
172
+ For enhanced timezone accuracy (optional):
173
+
174
+ ```bash
175
+ # Install optional timezone support
176
+ pip install isobar-cli[timezone]
177
+
178
+ # Sunrise/sunset will now display in local timezone
179
+ isobar London
180
+ ```
181
+
182
+ ### Debug Mode
183
+ Enable debug logging to stderr:
184
+
185
+ ```bash
186
+ # View API errors and debugging information
187
+ isobar "Test City" 2> debug.log
188
+ ```
189
+
148
190
  ## ⌨️ Shell Completion
149
191
 
150
192
  Isobar supports tab-completion for city names. To enable it for a shell:
@@ -212,6 +254,10 @@ Preparation Guidance:
212
254
  | `config.py` | Persistent home city configuration |
213
255
  | Enhanced `logic.py` | Preparation guidance, UV monitoring, gust alerts |
214
256
  | Updated `ui.py` | Contextual display of insights |
257
+ | **v1.2.0 Features** | **Security & Configuration** |
258
+ | Configurable API Endpoints | Environment variable support for custom APIs |
259
+ | Enhanced Error Handling | Specific exception catching with timeouts |
260
+ | Timezone Support | Optional `pytz` dependency for local time display |
215
261
 
216
262
  ## 🔒 Security
217
263
 
@@ -235,6 +281,7 @@ All security scans are integrated into the CI/CD pipeline and run on every push,
235
281
  ✅ **Phase 5 Complete** — Testing & Reliability
236
282
  ✅ **Phase 6 Complete** — Distribution (PyPI, Homebrew)
237
283
  ✅ **Phase 7 Complete** — Intuition & Analysis (v1.1.0)
284
+ ✅ **v1.2.0 Complete** — Security & Configuration Enhancements
238
285
  Refer to [ROADMAP.md](ROADMAP.md) and [CHANGELOG.md](CHANGELOG.md) for details.
239
286
 
240
287
  ## 🤝 Contributing
@@ -7,3 +7,6 @@ timezonefinder>=6.0.0
7
7
  pytest>=7.0.0
8
8
  requests-mock>=1.11.0
9
9
  pytest-cov>=4.1.0
10
+
11
+ [timezone]
12
+ pytz>=2024.1
File without changes
File without changes
File without changes
File without changes