jgtcore 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
jgtcore/__init__.py ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ jgtcore - Core library functions extracted from jgtutils
5
+
6
+ This module provides the core configuration, settings, and utility functions
7
+ from jgtutils without CLI dependencies, making them suitable for use in
8
+ other libraries and applications.
9
+
10
+ Main Functions:
11
+ - Configuration: readconfig(), get_config(), get_config_value()
12
+ - Settings: load_settings(), get_settings(), get_setting()
13
+ - Environment: setup_environment(), export_env_if_any()
14
+ - Utilities: str_to_datetime(), is_market_open(), print_exception()
15
+ - Trading: read_fx_str_from_config(), is_demo_mode()
16
+ """
17
+
18
+ from .core import (
19
+ # Core configuration functions
20
+ readconfig,
21
+ load_settings,
22
+ get_settings,
23
+
24
+ # Simple API wrappers
25
+ get_config,
26
+ get_setting,
27
+ setup_environment,
28
+ get_config_value,
29
+ is_demo_mode,
30
+
31
+ # Environment helpers
32
+ load_arg_from_jgt_env,
33
+ load_arg_default_from_settings,
34
+ export_env_if_any,
35
+
36
+ # Utility functions
37
+ str_to_datetime,
38
+ print_exception,
39
+ is_market_open,
40
+ dt_from_last_week_as_datetime,
41
+ dt_from_last_week_as_string_fxformat,
42
+
43
+ # Trading helpers
44
+ read_fx_str_from_config,
45
+
46
+ # Helper functions for advanced use
47
+ update_settings,
48
+ load_arg_default_from_settings_if_exist,
49
+ )
50
+
51
+ __version__ = "1.0.0"
52
+ __author__ = "JGWill"
53
+ __description__ = "Core library functions extracted from jgtutils"
54
+
55
+ # Export the main functions that external packages are likely to use
56
+ __all__ = [
57
+ # Configuration functions
58
+ 'readconfig',
59
+ 'load_settings',
60
+ 'get_settings',
61
+
62
+ # Simple API wrappers
63
+ 'get_config',
64
+ 'get_setting',
65
+ 'setup_environment',
66
+ 'get_config_value',
67
+ 'is_demo_mode',
68
+
69
+ # Environment helpers
70
+ 'load_arg_from_jgt_env',
71
+ 'load_arg_default_from_settings',
72
+ 'export_env_if_any',
73
+
74
+ # Utility functions
75
+ 'str_to_datetime',
76
+ 'print_exception',
77
+ 'is_market_open',
78
+ 'dt_from_last_week_as_datetime',
79
+ 'dt_from_last_week_as_string_fxformat',
80
+
81
+ # Trading helpers
82
+ 'read_fx_str_from_config',
83
+
84
+ # Helper functions
85
+ 'update_settings',
86
+ 'load_arg_default_from_settings_if_exist',
87
+ ]
jgtcore/core.py ADDED
@@ -0,0 +1,607 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright 2019 Gehtsoft USA LLC
4
+ # Copyright 2023 JGWill (extended/variations)
5
+
6
+ # Licensed under the license derived from the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+
9
+ # You may obtain a copy of the License at
10
+
11
+ # http://fxcodebase.com/licenses/open-source/license.html
12
+
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ """
20
+ jgtcore.core - Core library functions extracted from jgtutils
21
+ Provides configuration, settings, and utility functions without CLI dependencies.
22
+ """
23
+
24
+ import json
25
+ import ruamel.yaml
26
+ yaml = ruamel.yaml.YAML()
27
+
28
+ import os
29
+ import sys
30
+ import traceback
31
+ from datetime import datetime, time, timezone, timedelta
32
+ from typing import Dict, Tuple, Optional, Any
33
+
34
+ # Global variables for caching
35
+ settings: Dict[str, Any] = {}
36
+ _JGT_CONFIG_JSON_SECRET: Optional[str] = None
37
+
38
+ # Core Configuration Functions
39
+
40
+ def _load_settings_from_path(path: str) -> Dict[str, Any]:
41
+ """Load settings from a JSON file path."""
42
+ if os.path.exists(path):
43
+ with open(path, 'r') as f:
44
+ loaded_data = json.load(f)
45
+ return loaded_data
46
+ return {}
47
+
48
+
49
+ def _load_settings_from_path_yaml(path: str, key: Optional[str] = None) -> Dict[str, Any]:
50
+ """Load settings from a YAML file path, optionally extracting a specific key."""
51
+ if os.path.exists(path):
52
+ with open(path, 'r') as f:
53
+ if key is not None:
54
+ try:
55
+ yaml_value = yaml.load(f)
56
+ except yaml.YAMLError as exc:
57
+ print(exc)
58
+ if yaml_value is not None and key in yaml_value:
59
+ return yaml_value[key]
60
+ else:
61
+ return {}
62
+ yaml_data = yaml.load(f)
63
+ if yaml_data is None:
64
+ return {}
65
+ return yaml_data
66
+ return {}
67
+
68
+
69
+ def update_settings(old_settings: Dict[str, Any], new_settings: Dict[str, Any], keys: list = ['patterns']) -> None:
70
+ """Update old_settings with new_settings, handling special keys separately."""
71
+ # if our old settings has a key in our keys list, then we will update it on their own
72
+ # (meaning we will not merge it directly but update it independently)
73
+ for key in keys:
74
+ if key in old_settings:
75
+ if new_settings is not None and key in new_settings:
76
+ test_if_key_not_none = new_settings[key]
77
+ if test_if_key_not_none is not None:
78
+ old_settings[key].update(new_settings[key])
79
+ # remove the key from the new settings
80
+ new_settings.pop(key)
81
+ if new_settings is not None:
82
+ old_settings.update(new_settings)
83
+
84
+
85
+ def _settings_loaded(_settings: Dict[str, Any]) -> None:
86
+ """Hook called when settings are loaded. Override for custom behavior."""
87
+ return
88
+
89
+
90
+ def load_settings(custom_path: Optional[str] = None, old: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
91
+ """
92
+ Load settings from multiple locations in order of precedence.
93
+
94
+ Args:
95
+ custom_path: Optional custom path to settings file
96
+ old: Existing settings to merge with
97
+
98
+ Returns:
99
+ Merged settings dictionary
100
+ """
101
+ system_settings_path = os.path.join('/etc', 'jgt', 'settings.json')
102
+ home_settings_path = os.path.join(os.path.expanduser('~'), '.jgt', 'settings.json')
103
+ current_settings_path = os.path.join(os.getcwd(), '.jgt', 'settings.json')
104
+ yaml_current_settings_path = os.path.join(os.getcwd(), '.jgt', 'settings.yml')
105
+ jgt_yaml_current_settings_path = os.path.join(os.getcwd(), 'jgt.yml')
106
+ jubook_jgt_yaml_current_settings_path = os.path.join(os.getcwd(), '_config.yml')
107
+
108
+ _settings = {}
109
+ if old is not None:
110
+ _settings = old
111
+
112
+ # Load system settings
113
+ system_settings = _load_settings_from_path(system_settings_path)
114
+ update_settings(_settings, system_settings)
115
+
116
+ # Load from env JGT_SETTINGS_SYSTEM if exists
117
+ if 'JGT_SETTINGS_SYSTEM' in os.environ:
118
+ env_settings_system = json.loads(os.environ['JGT_SETTINGS_SYSTEM'])
119
+ update_settings(_settings, env_settings_system)
120
+
121
+ # Load user settings
122
+ user_settings = _load_settings_from_path(home_settings_path)
123
+ update_settings(_settings, user_settings)
124
+
125
+ # Load from env JGT_SETTINGS or JGT_SETTINGS_USER if exists
126
+ if 'JGT_SETTINGS' in os.environ:
127
+ env_settings_user = json.loads(os.environ['JGT_SETTINGS'])
128
+ update_settings(_settings, env_settings_user)
129
+
130
+ if 'JGT_SETTINGS_USER' in os.environ:
131
+ env_settings_user = json.loads(os.environ['JGT_SETTINGS_USER'])
132
+ update_settings(_settings, env_settings_user)
133
+
134
+ # Load current directory settings
135
+ current_settings = _load_settings_from_path(current_settings_path)
136
+ update_settings(_settings, current_settings)
137
+
138
+ current_settings_yaml = _load_settings_from_path_yaml(yaml_current_settings_path)
139
+ update_settings(_settings, current_settings_yaml)
140
+
141
+ jubook_jgt_current_settings_yaml = _load_settings_from_path_yaml(jubook_jgt_yaml_current_settings_path, key='jgt')
142
+ update_settings(_settings, jubook_jgt_current_settings_yaml)
143
+
144
+ jgt_current_settings_yaml = _load_settings_from_path_yaml(jgt_yaml_current_settings_path)
145
+ update_settings(_settings, jgt_current_settings_yaml)
146
+
147
+ # Load custom settings if provided
148
+ if custom_path is not None and custom_path != '':
149
+ custom_settings = {}
150
+ if '.json' in custom_path:
151
+ custom_settings = _load_settings_from_path(custom_path)
152
+ else:
153
+ if '.yml' in custom_path:
154
+ custom_settings = _load_settings_from_path_yaml(custom_path)
155
+ update_settings(_settings, custom_settings)
156
+
157
+ # Load process-level settings from environment
158
+ if 'JGT_SETTINGS_PROCESS' in os.environ:
159
+ env_settings_process = json.loads(os.environ['JGT_SETTINGS_PROCESS'])
160
+ update_settings(_settings, env_settings_process)
161
+
162
+ _settings_loaded(_settings)
163
+
164
+ return _settings
165
+
166
+
167
+ def get_settings(custom_path: Optional[str] = None) -> Dict[str, Any]:
168
+ """
169
+ Get cached settings, loading them if necessary.
170
+
171
+ Args:
172
+ custom_path: Optional custom path to settings file
173
+
174
+ Returns:
175
+ Settings dictionary
176
+ """
177
+ global settings
178
+ if settings is None or len(settings) == 0:
179
+ settings = load_settings(custom_path=custom_path)
180
+ return settings
181
+
182
+
183
+ def load_arg_default_from_settings(argname: str, default_value: Any, alias: Optional[str] = None,
184
+ from_jgt_env: bool = False, exclude_env_alias: bool = False) -> Any:
185
+ """
186
+ Load argument default value from settings.
187
+
188
+ Args:
189
+ argname: Argument name to look up
190
+ default_value: Default value if not found
191
+ alias: Alternative name to check
192
+ from_jgt_env: Whether to load from JGT environment
193
+ exclude_env_alias: Whether to exclude alias when loading from env
194
+
195
+ Returns:
196
+ Setting value or default
197
+ """
198
+ global settings
199
+ if settings is None or len(settings) == 0:
200
+ settings = load_settings()
201
+
202
+ _value = settings.get(argname, default_value)
203
+ if alias is not None and _value == default_value:
204
+ _value = settings.get(alias, default_value) # try alias might be used
205
+
206
+ if from_jgt_env:
207
+ _alias = None if exclude_env_alias else alias
208
+ _value = load_arg_from_jgt_env(argname, _alias)
209
+
210
+ return _value
211
+
212
+
213
+ def load_arg_from_jgt_env(argname: str, alias: Optional[str] = None) -> Optional[str]:
214
+ """
215
+ Load argument value from JGT environment variables.
216
+
217
+ Args:
218
+ argname: Argument name to look up
219
+ alias: Alternative name to check
220
+
221
+ Returns:
222
+ Environment variable value or None
223
+ """
224
+ _value = None
225
+ # Note: load_env() is from jgtenv, but we'll just check the environment directly
226
+ if argname in os.environ:
227
+ _value = os.getenv(argname, None)
228
+ if alias is not None and _value is None:
229
+ _value = os.getenv(alias, None)
230
+ return _value
231
+
232
+
233
+ def load_arg_default_from_settings_if_exist(argname: str, alias: Optional[str] = None) -> Optional[Any]:
234
+ """
235
+ Load argument default from settings only if it exists.
236
+
237
+ Args:
238
+ argname: Argument name to look up
239
+ alias: Alternative name to check
240
+
241
+ Returns:
242
+ Setting value or None
243
+ """
244
+ global settings
245
+ if settings is None or len(settings) == 0:
246
+ settings = load_settings()
247
+
248
+ _value = settings.get(argname, None)
249
+ if alias is not None and _value == None:
250
+ _value = settings.get(alias, None) # try alias might be used
251
+ return _value
252
+
253
+
254
+ def export_env_if_any(config: Dict[str, Any]) -> None:
255
+ """Export certain config values to environment variables if they exist."""
256
+ # if has a key : "keep_bid_ask" and if yes and set to "true", export an env variable "JGT_KEEP_BID_ASK" to "1"
257
+ if 'keep_bid_ask' in config and config['keep_bid_ask'] == True:
258
+ os.environ['JGT_KEEP_BID_ASK'] = '1'
259
+
260
+
261
+ def _set_demo_credential(config: Dict[str, Any], demo: bool = False) -> None:
262
+ """Set demo credentials in config if demo mode is enabled."""
263
+ if demo:
264
+ config["user_id"] = config["user_id_demo"]
265
+ config["password"] = config["password_demo"]
266
+ config["account"] = config["account_demo"]
267
+ config["connection"] = "Demo"
268
+
269
+
270
+ def readconfig(json_config_str: Optional[str] = None, config_file: str = 'config.json',
271
+ export_env: bool = False, config_file_path_env_name: str = 'JGT_CONFIG_PATH',
272
+ config_values_env_name: str = 'JGT_CONFIG', force_read_json: bool = False,
273
+ demo: bool = False, use_demo_json_config: bool = False) -> Dict[str, Any]:
274
+ """
275
+ Read configuration from various sources.
276
+
277
+ Args:
278
+ json_config_str: JSON string containing config
279
+ config_file: Config file name
280
+ export_env: Whether to export config to environment variables
281
+ config_file_path_env_name: Environment variable name for config file path
282
+ config_values_env_name: Environment variable name for config JSON
283
+ force_read_json: Force reading from default JSON file
284
+ demo: Whether to use demo credentials
285
+ use_demo_json_config: Whether to use demo-specific config file
286
+
287
+ Returns:
288
+ Configuration dictionary
289
+ """
290
+ global _JGT_CONFIG_JSON_SECRET
291
+
292
+ try:
293
+ home_dir = os.path.expanduser("~")
294
+ except:
295
+ home_dir = os.environ["HOME"]
296
+ if home_dir == "":
297
+ home_dir = os.environ["HOME"]
298
+
299
+ # demo_config are assumed to be $HOME/.jgt/config_demo.json
300
+ if demo and use_demo_json_config:
301
+ config_file = os.path.join(home_dir, '.jgt/config_demo.json')
302
+ # check if exist, advise and raise exception if not
303
+ if not os.path.exists(config_file):
304
+ print(f"Configuration not found. create : {config_file} or we will try to use the _demo in the usual config.json")
305
+ config = readconfig(force_read_json=True)
306
+ _set_demo_credential(config, demo)
307
+ return config
308
+
309
+ # force_read_json are assumed to be $HOME/.jgt/config.json
310
+ if force_read_json:
311
+ config_file = os.path.join(home_dir, '.jgt/config.json')
312
+ # check if exist, advise and raise exception if not
313
+ if not os.path.exists(config_file):
314
+ raise Exception(f"Configuration not found. create : {config_file})")
315
+ # load and return config
316
+ with open(config_file, 'r') as f:
317
+ config = json.load(f)
318
+ if export_env:
319
+ export_env_if_any(config)
320
+ _set_demo_credential(config, demo)
321
+ return config
322
+
323
+ # Try reading config from JSON string first
324
+ if json_config_str is not None:
325
+ config = json.loads(json_config_str)
326
+ _JGT_CONFIG_JSON_SECRET = json_config_str
327
+ if export_env:
328
+ export_env_if_any(config)
329
+ _set_demo_credential(config, demo)
330
+ return config
331
+
332
+ # Try cached config
333
+ if _JGT_CONFIG_JSON_SECRET is not None:
334
+ config = json.loads(_JGT_CONFIG_JSON_SECRET)
335
+ if export_env:
336
+ export_env_if_any(config)
337
+ _set_demo_credential(config, demo)
338
+ return config
339
+
340
+ config = None
341
+
342
+ # if file does not exist try set the path to the file in the HOME
343
+ if not os.path.exists(config_file):
344
+ config_file = os.path.join(home_dir, config_file)
345
+
346
+ if os.path.exists(config_file):
347
+ with open(config_file, 'r') as f:
348
+ config = json.load(f)
349
+ if export_env:
350
+ export_env_if_any(config)
351
+ _set_demo_credential(config, demo)
352
+ return config
353
+ else:
354
+ config_file = os.path.join(home_dir, config_file)
355
+ if os.path.isfile(config_file):
356
+ with open(config_file, 'r') as f:
357
+ config = json.load(f)
358
+ else:
359
+ # If config file still not found, try reading from environment variable
360
+ config_json_str = os.getenv('JGT_CONFIG_JSON_SECRET')
361
+ if config_json_str:
362
+ config = json.loads(config_json_str)
363
+ if export_env:
364
+ export_env_if_any(config)
365
+ _set_demo_credential(config, demo)
366
+ return config
367
+
368
+ # if file dont exist, try loading from env var JGT_CONFIG
369
+ if not os.path.exists(config_file):
370
+ config_json_str = os.getenv(config_values_env_name)
371
+
372
+ if config_json_str:
373
+ config = json.loads(config_json_str)
374
+ if export_env:
375
+ export_env_if_any(config)
376
+ else:
377
+ # if not found, try loading from env var JGT_CONFIG_PATH
378
+ config_file = os.getenv(config_file_path_env_name)
379
+ if config_file:
380
+ with open(config_file, 'r') as f:
381
+ config = json.load(f)
382
+ if export_env:
383
+ export_env_if_any(config)
384
+
385
+ # Read config file
386
+ if config is None:
387
+ if config_file is not None and os.path.exists(config_file):
388
+ with open(config_file, 'r') as file:
389
+ config = json.load(file)
390
+
391
+ if config is None:
392
+ # Last attempt to read
393
+ try:
394
+ another_config = "config.json"
395
+ if not os.path.exists(another_config):
396
+ another_config = os.path.join(os.path.expanduser('~'), '.jgt', 'config.json')
397
+
398
+ if not os.path.exists(another_config):
399
+ another_config = "/etc/jgt/config.json"
400
+ with open(another_config, 'r') as file:
401
+ config = json.load(file)
402
+ except:
403
+ pass
404
+
405
+ if config is None:
406
+ try:
407
+ with open("/home/jgi/.jgt/config.json", 'r') as file:
408
+ config = json.load(file)
409
+ except:
410
+ pass
411
+ if config is None:
412
+ try:
413
+ with open("/etc/jgt/config.json", 'r') as file:
414
+ config = json.load(file)
415
+ except:
416
+ pass
417
+
418
+ if config is None:
419
+ raise Exception(f"Configuration not found. Please provide a config file or set the JGT_CONFIG environment variable to the JSON config string. (config_file={config_file})")
420
+
421
+ if export_env:
422
+ export_env_if_any(config)
423
+ _set_demo_credential(config, demo)
424
+ return config
425
+
426
+
427
+ # Utility Functions
428
+
429
+ def str_to_datetime(date_str: str) -> Optional[datetime]:
430
+ """
431
+ Convert string to datetime using multiple format attempts.
432
+
433
+ Args:
434
+ date_str: Date string to parse
435
+
436
+ Returns:
437
+ datetime object or None if parsing fails
438
+ """
439
+ formats = ['%m.%d.%Y %H:%M:%S', '%Y-%m-%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y/%m/%d', '%Y-%m-%d']
440
+
441
+ for fmt in formats:
442
+ try:
443
+ return datetime.strptime(date_str, fmt)
444
+ except ValueError:
445
+ continue
446
+ return None
447
+
448
+
449
+ def print_exception(exception: Exception) -> None:
450
+ """Print exception with traceback."""
451
+ print("Exception: {0}\n{1}".format(exception, traceback.format_exc()))
452
+
453
+
454
+ def is_market_open(current_time: Optional[datetime] = None, exit_cli_if_closed: bool = False,
455
+ market_closed_callback: Optional[callable] = None) -> bool:
456
+ """
457
+ Check if forex market is currently open.
458
+
459
+ Args:
460
+ current_time: Time to check (defaults to now)
461
+ exit_cli_if_closed: Whether to exit if market is closed (not recommended for library use)
462
+ market_closed_callback: Callback to call if market is closed
463
+
464
+ Returns:
465
+ True if market is open
466
+ """
467
+ if current_time is None:
468
+ current_time = datetime.utcnow()
469
+
470
+ # Define market open and close times
471
+ market_open_time = time(21, 0) # 21:00 UTC
472
+ market_close_time = time(21, 15) # 21:15 UTC
473
+
474
+ # Get the current day of the week (0=Monday, 6=Sunday)
475
+ current_day = current_time.weekday()
476
+
477
+ current_time_utc = current_time.time()
478
+ # Check if the market is open
479
+ if current_day == 6: # Sunday
480
+ if current_time_utc >= market_open_time:
481
+ return True
482
+ elif current_day == 4: # Friday
483
+ if current_time_utc < market_close_time:
484
+ return True
485
+ elif 0 <= current_day < 4: # Monday to Thursday
486
+ return True
487
+
488
+ if market_closed_callback is not None:
489
+ market_closed_callback()
490
+ if exit_cli_if_closed:
491
+ print("Market is closed.")
492
+ sys.exit(1) # Simplified exit code for library use
493
+ return False
494
+
495
+
496
+ def dt_from_last_week_as_datetime() -> datetime:
497
+ """Get datetime from last week."""
498
+ today = datetime.now()
499
+ last_week = today - timedelta(days=7)
500
+ return last_week
501
+
502
+
503
+ def dt_from_last_week_as_string_fxformat() -> str:
504
+ """Get datetime from last week as FX format string."""
505
+ last_week = dt_from_last_week_as_datetime()
506
+ _str = last_week.strftime('%m.%d.%Y')
507
+ return _str + " 00:00:00"
508
+
509
+
510
+ def read_fx_str_from_config(demo: bool = False) -> Tuple[str, str, str, str, str]:
511
+ """
512
+ Read FX connection parameters from config.
513
+
514
+ Args:
515
+ demo: Whether to use demo credentials
516
+
517
+ Returns:
518
+ Tuple of (user_id, password, url, connection, account)
519
+ """
520
+ config = readconfig(demo=demo)
521
+ if config["connection"] == "Real" and demo: # Make sure we have our demo credentials
522
+ _set_demo_credential(config, True)
523
+ str_user_id = config['user_id']
524
+ str_password = config['password']
525
+ str_url = config['url']
526
+ str_connection = "Real" if not demo else "Demo"
527
+ str_account = config['account']
528
+ return str_user_id, str_password, str_url, str_connection, str_account
529
+
530
+
531
+ # Simple API wrappers for external packages
532
+
533
+ def get_config(demo: bool = False, export_env: bool = False) -> Dict[str, Any]:
534
+ """
535
+ Simple configuration loader for external packages.
536
+
537
+ Args:
538
+ demo: Whether to use demo credentials
539
+ export_env: Whether to export config to environment variables
540
+
541
+ Returns:
542
+ Configuration dictionary
543
+ """
544
+ return readconfig(demo=demo, export_env=export_env)
545
+
546
+
547
+ def get_setting(key: str, default: Any = None, custom_path: Optional[str] = None) -> Any:
548
+ """
549
+ Get a single setting value.
550
+
551
+ Args:
552
+ key: Setting key to retrieve
553
+ default: Default value if key not found
554
+ custom_path: Optional custom path to settings file
555
+
556
+ Returns:
557
+ Setting value or default
558
+ """
559
+ settings = get_settings(custom_path=custom_path)
560
+ return settings.get(key, default)
561
+
562
+
563
+ def setup_environment(demo: bool = False, custom_settings_path: Optional[str] = None) -> Tuple[Dict[str, Any], Dict[str, Any]]:
564
+ """
565
+ One-call setup for external packages.
566
+ Sets up configuration and settings, exports environment variables.
567
+
568
+ Args:
569
+ demo: Whether to use demo credentials
570
+ custom_settings_path: Optional custom path to settings file
571
+
572
+ Returns:
573
+ Tuple of (config_dict, settings_dict)
574
+ """
575
+ config = readconfig(demo=demo, export_env=True)
576
+ settings = get_settings(custom_path=custom_settings_path)
577
+ return config, settings
578
+
579
+
580
+ def get_config_value(key: str, default: Any = None, demo: bool = False) -> Any:
581
+ """
582
+ Get a single configuration value.
583
+
584
+ Args:
585
+ key: Configuration key to retrieve
586
+ default: Default value if key not found
587
+ demo: Whether to use demo credentials
588
+
589
+ Returns:
590
+ Configuration value or default
591
+ """
592
+ config = readconfig(demo=demo)
593
+ return config.get(key, default)
594
+
595
+
596
+ def is_demo_mode() -> bool:
597
+ """
598
+ Check if running in demo mode based on current configuration.
599
+
600
+ Returns:
601
+ True if demo mode is active
602
+ """
603
+ try:
604
+ config = readconfig()
605
+ return config.get('connection', '').lower() == 'demo'
606
+ except:
607
+ return False
jgtcore/example.py ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ Example usage of jgtcore library
5
+
6
+ This demonstrates how to use the extracted core functions
7
+ from jgtutils without CLI dependencies.
8
+ """
9
+
10
+ import sys
11
+ import os
12
+
13
+ # Add the current directory to Python path so we can import jgtcore
14
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
15
+
16
+ import core as jgtcore
17
+
18
+
19
+ def main():
20
+ """Example usage of jgtcore functions."""
21
+
22
+ print("=== jgtcore Example Usage ===\n")
23
+
24
+ # 1. Simple setup for external packages
25
+ print("1. Setting up environment...")
26
+ try:
27
+ config, settings = jgtcore.setup_environment(demo=True)
28
+ print(f" ✓ Config loaded with {len(config)} keys")
29
+ print(f" ✓ Settings loaded with {len(settings)} keys")
30
+ except Exception as e:
31
+ print(f" ✗ Setup failed: {e}")
32
+
33
+ # 2. Get specific configuration values
34
+ print("\n2. Getting configuration values...")
35
+ try:
36
+ url = jgtcore.get_config_value('url', 'default-url')
37
+ print(f" URL: {url}")
38
+
39
+ demo_mode = jgtcore.is_demo_mode()
40
+ print(f" Demo mode: {demo_mode}")
41
+ except Exception as e:
42
+ print(f" ✗ Config access failed: {e}")
43
+
44
+ # 3. Get specific settings
45
+ print("\n3. Getting settings...")
46
+ try:
47
+ timeframe = jgtcore.get_setting('timeframe', 'H1')
48
+ instrument = jgtcore.get_setting('instrument', 'EUR/USD')
49
+ print(f" Default timeframe: {timeframe}")
50
+ print(f" Default instrument: {instrument}")
51
+ except Exception as e:
52
+ print(f" ✗ Settings access failed: {e}")
53
+
54
+ # 4. Utility functions
55
+ print("\n4. Utility functions...")
56
+
57
+ # Date/time utilities
58
+ last_week = jgtcore.dt_from_last_week_as_string_fxformat()
59
+ print(f" Last week (FX format): {last_week}")
60
+
61
+ # Market status
62
+ market_open = jgtcore.is_market_open()
63
+ print(f" Market open: {market_open}")
64
+
65
+ # String to datetime conversion
66
+ test_date = "2024-01-15 10:30:00"
67
+ parsed_date = jgtcore.str_to_datetime(test_date)
68
+ print(f" Parsed date '{test_date}': {parsed_date}")
69
+
70
+ # 5. Trading connection parameters
71
+ print("\n5. Trading connection...")
72
+ try:
73
+ user_id, password, url, connection, account = jgtcore.read_fx_str_from_config(demo=True)
74
+ print(f" Connection: {connection}")
75
+ print(f" URL: {url}")
76
+ print(f" Account: {account}")
77
+ print(f" User ID: {user_id[:3]}... (hidden)")
78
+ except Exception as e:
79
+ print(f" ✗ Trading config failed: {e}")
80
+
81
+ print("\n=== Example completed ===")
82
+
83
+
84
+ if __name__ == "__main__":
85
+ main()
jgtcore/test_core.py ADDED
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ Test script for jgtcore library
5
+
6
+ Tests the core functionality extracted from jgtutils
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import tempfile
12
+ import json
13
+ from datetime import datetime
14
+
15
+ # Add jgtcore to path
16
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
17
+
18
+ import core
19
+
20
+
21
+ def test_str_to_datetime():
22
+ """Test string to datetime conversion."""
23
+ print("Testing str_to_datetime...")
24
+
25
+ test_cases = [
26
+ ("2024-01-15 10:30:00", True),
27
+ ("01.15.2024 10:30:00", True),
28
+ ("2024/01/15", True),
29
+ ("invalid-date", False),
30
+ ("", False)
31
+ ]
32
+
33
+ for date_str, should_succeed in test_cases:
34
+ result = core.str_to_datetime(date_str)
35
+ if should_succeed:
36
+ assert result is not None, f"Expected {date_str} to parse successfully"
37
+ assert isinstance(result, datetime), f"Expected datetime object for {date_str}"
38
+ print(f" ✓ {date_str} -> {result}")
39
+ else:
40
+ assert result is None, f"Expected {date_str} to fail parsing"
41
+ print(f" ✓ {date_str} -> None (as expected)")
42
+
43
+
44
+ def test_market_open():
45
+ """Test market open functionality."""
46
+ print("Testing is_market_open...")
47
+
48
+ # Test with current time
49
+ result = core.is_market_open()
50
+ print(f" Current market status: {'Open' if result else 'Closed'}")
51
+
52
+ # Test with specific times (without exit behavior)
53
+ test_time = datetime(2024, 1, 15, 10, 0) # Monday 10:00 UTC
54
+ result = core.is_market_open(test_time)
55
+ print(f" Monday 10:00 UTC: {'Open' if result else 'Closed'}")
56
+
57
+
58
+ def test_datetime_helpers():
59
+ """Test datetime helper functions."""
60
+ print("Testing datetime helpers...")
61
+
62
+ last_week_dt = core.dt_from_last_week_as_datetime()
63
+ last_week_str = core.dt_from_last_week_as_string_fxformat()
64
+
65
+ assert isinstance(last_week_dt, datetime), "Expected datetime object"
66
+ assert isinstance(last_week_str, str), "Expected string"
67
+ assert "00:00:00" in last_week_str, "Expected time suffix in FX format"
68
+
69
+ print(f" ✓ Last week datetime: {last_week_dt}")
70
+ print(f" ✓ Last week FX format: {last_week_str}")
71
+
72
+
73
+ def test_settings_with_temp_file():
74
+ """Test settings loading with temporary files."""
75
+ print("Testing settings loading...")
76
+
77
+ # Create temporary settings file
78
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
79
+ test_settings = {
80
+ "instrument": "GBP/USD",
81
+ "timeframe": "M15",
82
+ "test_value": 42
83
+ }
84
+ json.dump(test_settings, f)
85
+ temp_path = f.name
86
+
87
+ try:
88
+ # Test loading settings from custom path
89
+ settings = core.load_settings(custom_path=temp_path)
90
+
91
+ assert "instrument" in settings, "Expected instrument in settings"
92
+ assert settings["instrument"] == "GBP/USD", "Expected correct instrument value"
93
+ assert settings["timeframe"] == "M15", "Expected correct timeframe value"
94
+ assert settings["test_value"] == 42, "Expected correct test value"
95
+
96
+ print(f" ✓ Loaded settings: {len(settings)} keys")
97
+ print(f" ✓ Instrument: {settings['instrument']}")
98
+ print(f" ✓ Timeframe: {settings['timeframe']}")
99
+
100
+ # Test get_settings caching
101
+ cached_settings = core.get_settings()
102
+ print(f" ✓ Cached settings: {len(cached_settings)} keys")
103
+
104
+ finally:
105
+ # Clean up
106
+ os.unlink(temp_path)
107
+
108
+
109
+ def test_config_loading():
110
+ """Test configuration loading."""
111
+ print("Testing configuration loading...")
112
+
113
+ # Create temporary config file
114
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
115
+ test_config = {
116
+ "url": "https://test.example.com",
117
+ "user_id": "test_user",
118
+ "password": "test_pass",
119
+ "account": "test_account",
120
+ "connection": "Demo",
121
+ "user_id_demo": "demo_user",
122
+ "password_demo": "demo_pass",
123
+ "account_demo": "demo_account"
124
+ }
125
+ json.dump(test_config, f)
126
+ temp_path = f.name
127
+
128
+ try:
129
+ # Test direct JSON string loading
130
+ config_str = json.dumps(test_config)
131
+ config = core.readconfig(json_config_str=config_str)
132
+
133
+ assert config["url"] == "https://test.example.com", "Expected correct URL"
134
+ assert config["user_id"] == "test_user", "Expected correct user ID"
135
+
136
+ print(f" ✓ Config from JSON string: {len(config)} keys")
137
+
138
+ # Test demo mode
139
+ demo_config = core.readconfig(json_config_str=config_str, demo=True)
140
+ assert demo_config["connection"] == "Demo", "Expected demo connection"
141
+ assert demo_config["user_id"] == "demo_user", "Expected demo user ID"
142
+
143
+ print(f" ✓ Demo mode: {demo_config['connection']}")
144
+
145
+ finally:
146
+ # Clean up
147
+ os.unlink(temp_path)
148
+
149
+
150
+ def test_api_wrappers():
151
+ """Test simple API wrapper functions."""
152
+ print("Testing API wrappers...")
153
+
154
+ # Create test config
155
+ test_config = {
156
+ "url": "https://api.example.com",
157
+ "connection": "Demo",
158
+ "test_key": "test_value"
159
+ }
160
+ config_str = json.dumps(test_config)
161
+
162
+ # Test get_config
163
+ config = core.get_config()
164
+ print(f" ✓ get_config returned {len(config)} keys")
165
+
166
+ # Test get_config_value with JSON string
167
+ os.environ['JGT_CONFIG_JSON_SECRET'] = config_str
168
+ value = core.get_config_value('test_key', 'default')
169
+ # Clean up environment
170
+ del os.environ['JGT_CONFIG_JSON_SECRET']
171
+
172
+ print(f" ✓ get_config_value: {value}")
173
+
174
+ # Test is_demo_mode
175
+ demo_mode = core.is_demo_mode()
176
+ print(f" ✓ is_demo_mode: {demo_mode}")
177
+
178
+
179
+ def main():
180
+ """Run all tests."""
181
+ print("=== jgtcore Test Suite ===\n")
182
+
183
+ try:
184
+ test_str_to_datetime()
185
+ print()
186
+
187
+ test_market_open()
188
+ print()
189
+
190
+ test_datetime_helpers()
191
+ print()
192
+
193
+ test_settings_with_temp_file()
194
+ print()
195
+
196
+ test_config_loading()
197
+ print()
198
+
199
+ test_api_wrappers()
200
+
201
+ print("\n=== All tests passed! ===")
202
+
203
+ except Exception as e:
204
+ print(f"\n❌ Test failed: {e}")
205
+ core.print_exception(e)
206
+ sys.exit(1)
207
+
208
+
209
+ if __name__ == "__main__":
210
+ main()
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Guillaume Descoteaux-Isabelle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.1
2
+ Name: jgtcore
3
+ Version: 0.1.0
4
+ Summary: Core library for JGT utilities - configuration, settings, and utility functions
5
+ Author-email: Guillaume Isabelle <jgi@jgwill.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/jgwill/jgtcore
8
+ Project-URL: Bug Tracker, https://github.com/jgwill/jgtcore/issues
9
+ Project-URL: Documentation, https://github.com/jgwill/jgtcore#readme
10
+ Project-URL: Source Code, https://github.com/jgwill/jgtcore
11
+ Keywords: configuration,settings,utilities,jgt
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.7
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Requires-Python: >=3.7
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: python-dotenv
24
+ Requires-Dist: ruamel.yaml (>=0.18.6)
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest ; extra == 'dev'
27
+ Requires-Dist: pytest-cov ; extra == 'dev'
28
+ Requires-Dist: black ; extra == 'dev'
29
+ Requires-Dist: isort ; extra == 'dev'
30
+ Requires-Dist: flake8 ; extra == 'dev'
31
+
32
+ # jgtcore
33
+
34
+ Core library for JGT utilities - configuration, settings, and utility functions.
35
+
36
+ This is a pure Python library extracted from `jgtutils` to provide clean programmatic access to JGT configuration and settings without CLI dependencies.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install jgtcore
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ### Simple Configuration Access
47
+ ```python
48
+ import jgtcore
49
+
50
+ # Load configuration
51
+ config = jgtcore.get_config()
52
+ demo_config = jgtcore.get_config(demo=True)
53
+
54
+ # Get specific config values
55
+ user_id = jgtcore.get_config_value('user_id')
56
+ quotes_count = jgtcore.get_config_value('quotes_count', 1000)
57
+ ```
58
+
59
+ ### Settings Management
60
+ ```python
61
+ import jgtcore
62
+
63
+ # Get all settings
64
+ settings = jgtcore.get_settings()
65
+
66
+ # Get specific setting with default
67
+ instrument = jgtcore.get_setting('instrument', 'EUR/USD')
68
+ timeframes = jgtcore.get_setting('_timeframes', 'D1')
69
+ ```
70
+
71
+ ### Environment Setup
72
+ ```python
73
+ import jgtcore
74
+
75
+ # One-call environment setup
76
+ config, settings = jgtcore.setup_environment(demo=True)
77
+
78
+ # Check demo mode
79
+ if jgtcore.is_demo_mode():
80
+ print("Running in demo mode")
81
+ ```
82
+
83
+ ### Advanced Usage
84
+ ```python
85
+ from jgtcore import readconfig, load_settings
86
+
87
+ # Load with specific options
88
+ config = readconfig(demo=True, export_env=True)
89
+ settings = load_settings(custom_path="/path/to/settings.json")
90
+ ```
91
+
92
+ ## Configuration Files
93
+
94
+ - **config.json**: Trading credentials and connection settings
95
+ - **settings.json**: Application settings and preferences
96
+
97
+ See `examples/` directory for sample configuration files.
98
+
99
+ ## File Locations
100
+
101
+ ### config.json lookup order:
102
+ 1. Current directory: `config.json`
103
+ 2. User home: `~/.jgt/config.json`
104
+ 3. System: `/etc/jgt/config.json`
105
+ 4. Environment variables: `JGT_CONFIG`, `JGT_CONFIG_PATH`
106
+
107
+ ### settings.json lookup order:
108
+ 1. System: `/etc/jgt/settings.json`
109
+ 2. User home: `~/.jgt/settings.json`
110
+ 3. Current directory: `.jgt/settings.json`
111
+ 4. YAML variants: `jgt.yml`, `_config.yml`
112
+ 5. Environment variables: `JGT_SETTINGS`
113
+
114
+ ## License
115
+
116
+ MIT License
@@ -0,0 +1,9 @@
1
+ jgtcore/__init__.py,sha256=dqVduCjlUX03mQIORp8Jf_NZuRhaQ4l8Eb76pNAIWfs,2159
2
+ jgtcore/core.py,sha256=hOfj_SK8yBMvroWmSQSm9hcGEFb5l7g_m-LEIuCcHAQ,21409
3
+ jgtcore/example.py,sha256=FnHo1NR0zFllAhk4Z590w-ZtlXdA9QH0oht4MPotViY,2631
4
+ jgtcore/test_core.py,sha256=xqCvRNa2MQrSoaLa6ACLZrTvkf4Bxx_RIk5fwlflBjo,6432
5
+ jgtcore-0.1.0.dist-info/LICENSE,sha256=uCA_HysYVK9qIMbDEFjcDYhCoKTg19AJhKhM71EKSGA,1086
6
+ jgtcore-0.1.0.dist-info/METADATA,sha256=GCdtDp84Sr5YOO56rknyrUSyjD3lXl3kTkCU7Pt4NgI,3234
7
+ jgtcore-0.1.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
8
+ jgtcore-0.1.0.dist-info/top_level.txt,sha256=Chh0dpqpr0NjebGncKO5fsd_FaQzPyUb0Q0WTTfuPsA,8
9
+ jgtcore-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.38.4)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ jgtcore