config2py 0.1.39__tar.gz → 0.1.40__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.
- {config2py-0.1.39 → config2py-0.1.40}/PKG-INFO +1 -1
- {config2py-0.1.39 → config2py-0.1.40}/config2py/util.py +139 -64
- {config2py-0.1.39 → config2py-0.1.40}/config2py.egg-info/PKG-INFO +1 -1
- {config2py-0.1.39 → config2py-0.1.40}/setup.cfg +1 -1
- {config2py-0.1.39 → config2py-0.1.40}/LICENSE +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/README.md +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py/__init__.py +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py/base.py +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py/errors.py +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py/s_configparser.py +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py/scrap/__init__.py +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py/tests/__init__.py +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py/tests/test_tools.py +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py/tests/utils_for_testing.py +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py/tools.py +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py.egg-info/SOURCES.txt +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py.egg-info/dependency_links.txt +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py.egg-info/not-zip-safe +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py.egg-info/requires.txt +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/config2py.egg-info/top_level.txt +0 -0
- {config2py-0.1.39 → config2py-0.1.40}/setup.py +0 -0
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
import re
|
|
4
4
|
import os
|
|
5
5
|
import ast
|
|
6
|
-
from collections import ChainMap
|
|
6
|
+
from collections import ChainMap, namedtuple
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Optional, Union, Any, Callable, Set, Literal,
|
|
8
|
+
from typing import Optional, Union, Any, Callable, Set, Literal, get_args
|
|
9
|
+
from types import SimpleNamespace
|
|
9
10
|
import getpass
|
|
10
11
|
|
|
11
12
|
from dol import process_path
|
|
@@ -18,8 +19,6 @@ from i2 import mk_sentinel # TODO: Only i2 dependency. Consider replacing.
|
|
|
18
19
|
DFLT_APP_NAME = "config2py"
|
|
19
20
|
DFLT_MASKING_INPUT = False
|
|
20
21
|
|
|
21
|
-
AppFolderKind = Literal["data", "config", "cache"]
|
|
22
|
-
|
|
23
22
|
not_found = mk_sentinel("not_found")
|
|
24
23
|
no_default = mk_sentinel("no_default")
|
|
25
24
|
|
|
@@ -196,22 +195,6 @@ def extract_variable_declarations(
|
|
|
196
195
|
return env_vars
|
|
197
196
|
|
|
198
197
|
|
|
199
|
-
def _system_default_for_app_data_folder():
|
|
200
|
-
"""Get the system default for the app data folder."""
|
|
201
|
-
if os.name == "nt":
|
|
202
|
-
# Windows
|
|
203
|
-
app_data_folder = os.getenv("APPDATA")
|
|
204
|
-
else:
|
|
205
|
-
# macOS and Linux/Unix
|
|
206
|
-
app_data_folder = os.path.expanduser("~/.config")
|
|
207
|
-
return app_data_folder
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
DFLT_APP_DATA_FOLDER = os.getenv(
|
|
211
|
-
"CONFIG2PY_APP_DATA_FOLDER", _system_default_for_app_data_folder()
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
|
|
215
198
|
def create_directories(dirpath, max_dirs_to_make=None):
|
|
216
199
|
"""
|
|
217
200
|
Create directories up to a specified limit.
|
|
@@ -274,36 +257,119 @@ def create_directories(dirpath, max_dirs_to_make=None):
|
|
|
274
257
|
return True
|
|
275
258
|
|
|
276
259
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
260
|
+
FolderSpec = namedtuple("FolderSpec", ["env_var", "default_path"])
|
|
261
|
+
|
|
262
|
+
if os.name == "nt":
|
|
263
|
+
APP_FOLDER_STANDARDS = dict(
|
|
264
|
+
config=FolderSpec("APPDATA", os.getenv("APPDATA", "")),
|
|
265
|
+
data=FolderSpec("LOCALAPPDATA", os.getenv("LOCALAPPDATA", "")),
|
|
266
|
+
cache=FolderSpec(
|
|
267
|
+
"LOCALAPPDATA", os.path.join(os.getenv("LOCALAPPDATA", ""), "Temp")
|
|
268
|
+
),
|
|
269
|
+
state=FolderSpec("LOCALAPPDATA", os.getenv("LOCALAPPDATA", "")),
|
|
270
|
+
runtime=FolderSpec("TEMP", os.getenv("TEMP", "")),
|
|
271
|
+
)
|
|
272
|
+
else:
|
|
273
|
+
APP_FOLDER_STANDARDS = dict(
|
|
274
|
+
config=FolderSpec("XDG_CONFIG_HOME", "~/.config"),
|
|
275
|
+
data=FolderSpec("XDG_DATA_HOME", "~/.local/share"),
|
|
276
|
+
cache=FolderSpec("XDG_CACHE_HOME", "~/.cache"),
|
|
277
|
+
state=FolderSpec("XDG_STATE_HOME", "~/.local/state"),
|
|
278
|
+
runtime=FolderSpec("XDG_RUNTIME_DIR", "/tmp"),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
AppFolderKind = Literal["config", "data", "cache", "state", "runtime"]
|
|
283
|
+
|
|
284
|
+
# Verify AppFolderKind matches _APP_FOLDER_STANDARDS_DICT keys
|
|
285
|
+
# Note: This is due to the fact that static type checkers can't verify
|
|
286
|
+
# that the keys of _APP_FOLDER_STANDARDS_DICT match the Literal values.
|
|
287
|
+
# This breaks SSOT, but here we at least validate alignment at runtime.
|
|
288
|
+
_literal_kinds = get_args(AppFolderKind)
|
|
289
|
+
assert set(_literal_kinds) == set(APP_FOLDER_STANDARDS.keys()), (
|
|
290
|
+
f"AppFolderKind Literal {_literal_kinds} doesn't match "
|
|
291
|
+
f"APP_FOLDER_STANDARDS keys {tuple(APP_FOLDER_STANDARDS.keys())}"
|
|
292
|
+
)
|
|
281
293
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
294
|
+
config2py_env_var = SimpleNamespace(
|
|
295
|
+
**{k: f"CONFIG2PY_{k.upper()}_DIR" for k in APP_FOLDER_STANDARDS}
|
|
296
|
+
)
|
|
285
297
|
|
|
286
|
-
Returns:
|
|
287
|
-
str: The full path of the app data folder.
|
|
288
298
|
|
|
289
|
-
|
|
299
|
+
DFLT_APP_FOLDER_KIND: AppFolderKind = "config" # type: ignore (for <3.11)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def system_default_for_app_data_folder(
|
|
303
|
+
folder_kind: AppFolderKind = DFLT_APP_FOLDER_KIND, # type: ignore (for <3.11)
|
|
304
|
+
) -> str:
|
|
305
|
+
"""Get the system default for the app data folder."""
|
|
306
|
+
# Platform-specific specs: (env_var, default_path)
|
|
307
|
+
|
|
308
|
+
# Same logic for both platforms: check env var, then use default
|
|
309
|
+
env_var, default = APP_FOLDER_STANDARDS[folder_kind]
|
|
310
|
+
return os.path.expanduser(os.getenv(env_var, default))
|
|
311
|
+
|
|
290
312
|
|
|
291
|
-
|
|
292
|
-
|
|
313
|
+
DFLT_CONFIG_FOLDER = system_default_for_app_data_folder("config")
|
|
314
|
+
DFLT_DATA_FOLDER = system_default_for_app_data_folder("data")
|
|
315
|
+
DFLT_CACHE_FOLDER = system_default_for_app_data_folder("cache")
|
|
316
|
+
DFLT_STATE_FOLDER = system_default_for_app_data_folder("state")
|
|
317
|
+
DFLT_RUNTIME_FOLDER = system_default_for_app_data_folder("runtime")
|
|
293
318
|
|
|
294
|
-
If ``ensure_exists`` is ``True`` (the default), the folder will be created if
|
|
295
|
-
it doesn't exist.
|
|
296
319
|
|
|
297
|
-
|
|
298
|
-
|
|
320
|
+
def get_app_rootdir(
|
|
321
|
+
folder_kind: AppFolderKind = DFLT_APP_FOLDER_KIND, # type: ignore (for <3.11)
|
|
322
|
+
*,
|
|
323
|
+
ensure_exists: bool = True,
|
|
324
|
+
) -> str:
|
|
325
|
+
"""
|
|
326
|
+
Returns the root directory for a specific folder kind.
|
|
327
|
+
|
|
328
|
+
The folder kind determines which standard directory is returned:
|
|
329
|
+
- 'config': Configuration files (XDG_CONFIG_HOME, default ~/.config)
|
|
330
|
+
- 'data': Application data (XDG_DATA_HOME, default ~/.local/share)
|
|
331
|
+
- 'cache': Temporary/cache files (XDG_CACHE_HOME, default ~/.cache)
|
|
332
|
+
- 'state': State data/logs (XDG_STATE_HOME, default ~/.local/state)
|
|
333
|
+
- 'runtime': Runtime files (XDG_RUNTIME_DIR, default /tmp)
|
|
334
|
+
|
|
335
|
+
On Windows:
|
|
336
|
+
- 'config': %APPDATA%
|
|
337
|
+
- 'data': %LOCALAPPDATA%
|
|
338
|
+
- 'cache': %LOCALAPPDATA%\\Temp
|
|
339
|
+
- 'state': %LOCALAPPDATA%
|
|
340
|
+
- 'runtime': %TEMP%
|
|
299
341
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
to use.
|
|
342
|
+
Args:
|
|
343
|
+
folder_kind: Type of folder ('config', 'data', 'cache', 'state', or 'runtime')
|
|
344
|
+
ensure_exists: Whether to create the directory if it doesn't exist
|
|
304
345
|
|
|
346
|
+
Returns:
|
|
347
|
+
str: The full path of the app root folder for the specified kind.
|
|
348
|
+
|
|
349
|
+
Note: The default root folder follows XDG Base Directory standards on Unix/Linux/macOS.
|
|
350
|
+
You can override this by setting environment variables:
|
|
351
|
+
- CONFIG2PY_CONFIG_FOLDER, CONFIG2PY_DATA_FOLDER, CONFIG2PY_CACHE_FOLDER, etc.
|
|
352
|
+
(highest priority, overrides everything)
|
|
353
|
+
- XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME, etc.
|
|
354
|
+
(standard XDG override)
|
|
355
|
+
- If neither is set, uses platform defaults
|
|
356
|
+
|
|
357
|
+
Examples:
|
|
358
|
+
>>> get_app_rootdir('config') # doctest: +SKIP
|
|
359
|
+
'/Users/.../.config'
|
|
360
|
+
>>> get_app_rootdir('data') # doctest: +SKIP
|
|
361
|
+
'/Users/.../.local/share'
|
|
362
|
+
>>> get_app_rootdir('cache') # doctest: +SKIP
|
|
363
|
+
'/Users/.../.cache'
|
|
305
364
|
"""
|
|
306
|
-
|
|
365
|
+
folderpath = os.getenv(
|
|
366
|
+
getattr(config2py_env_var, folder_kind), # Check config2py custom env var first
|
|
367
|
+
system_default_for_app_data_folder(
|
|
368
|
+
folder_kind
|
|
369
|
+
), # ... if not, use system default
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
return process_path(folderpath, ensure_dir_exists=ensure_exists)
|
|
307
373
|
|
|
308
374
|
|
|
309
375
|
# renaming get_app_data_rootdir to get_app_rootdir
|
|
@@ -329,9 +395,6 @@ def _default_folder_setup(directory_path: str) -> None:
|
|
|
329
395
|
(Path(directory_path) / ".config2py").write_text("Created by config2py.")
|
|
330
396
|
|
|
331
397
|
|
|
332
|
-
DFLT_APP_FOLDER_KIND: AppFolderKind = "config"
|
|
333
|
-
|
|
334
|
-
|
|
335
398
|
def get_app_data_folder(
|
|
336
399
|
app_name: str = DFLT_APP_NAME,
|
|
337
400
|
*,
|
|
@@ -340,41 +403,53 @@ def get_app_data_folder(
|
|
|
340
403
|
folder_kind: AppFolderKind = DFLT_APP_FOLDER_KIND,
|
|
341
404
|
) -> str:
|
|
342
405
|
"""
|
|
343
|
-
Retrieve or create the app data directory specific to the given app name.
|
|
406
|
+
Retrieve or create the app data directory specific to the given app name and folder kind.
|
|
407
|
+
|
|
408
|
+
The folder kind determines where the app's files are stored:
|
|
409
|
+
- 'config': For configuration/settings files
|
|
410
|
+
- 'data': For essential user data, databases, sessions
|
|
411
|
+
- 'cache': For temporary/regeneratable data
|
|
344
412
|
|
|
345
413
|
Args:
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
414
|
+
app_name: Name of the app for which the data directory is needed.
|
|
415
|
+
setup_callback: A callback function to initialize the directory.
|
|
416
|
+
Default is _default_folder_setup.
|
|
417
|
+
ensure_exists: Whether to ensure the directory exists.
|
|
418
|
+
folder_kind: Type of folder ('config', 'data', or 'cache').
|
|
419
|
+
Default is 'config' for backward compatibility.
|
|
350
420
|
|
|
351
421
|
Returns:
|
|
352
|
-
|
|
422
|
+
str: Path to the app data directory.
|
|
353
423
|
|
|
354
|
-
By default, the app will be "config2py":
|
|
424
|
+
By default, the app will be "config2py" and folder_kind will be "config":
|
|
355
425
|
|
|
356
426
|
>>> get_app_data_folder() # doctest: +ELLIPSIS
|
|
357
427
|
'.../.config/config2py'
|
|
358
428
|
|
|
359
|
-
You can specify a different app name
|
|
360
|
-
And if you want, you can also specify a callback function to initialize the
|
|
361
|
-
directory.
|
|
429
|
+
You can specify a different app name and folder kind:
|
|
362
430
|
|
|
363
|
-
>>>
|
|
364
|
-
|
|
365
|
-
'
|
|
366
|
-
|
|
431
|
+
>>> get_app_data_folder('my_app', folder_kind='data') # doctest: +SKIP
|
|
432
|
+
'/Users/.../.local/share/my_app'
|
|
433
|
+
>>> get_app_data_folder('my_app', folder_kind='cache') # doctest: +SKIP
|
|
434
|
+
'/Users/.../.cache/my_app'
|
|
367
435
|
|
|
368
|
-
You can also specify a path relative to the app
|
|
369
|
-
(on linux/mac systems, this is typically ~/.config)
|
|
436
|
+
You can also specify a path relative to the app root directory:
|
|
370
437
|
|
|
371
|
-
>>> get_app_data_folder('another/app/
|
|
372
|
-
'/Users/.../.
|
|
438
|
+
>>> get_app_data_folder('another/app/subfolder', folder_kind='data') # doctest: +SKIP
|
|
439
|
+
'/Users/.../.local/share/another/app/subfolder'
|
|
373
440
|
|
|
441
|
+
If ensure_exists is True, the directory will be created and initialized
|
|
442
|
+
with the setup_callback:
|
|
443
|
+
|
|
444
|
+
>>> path = get_app_data_folder('my_app', ensure_exists=True) # doctest: +SKIP
|
|
445
|
+
>>> os.path.exists(path) # doctest: +SKIP
|
|
446
|
+
True
|
|
374
447
|
"""
|
|
375
|
-
app_data_path = os.path.join(
|
|
448
|
+
app_data_path = os.path.join(
|
|
449
|
+
get_app_rootdir(folder_kind, ensure_exists=ensure_exists), app_name
|
|
450
|
+
)
|
|
376
451
|
app_data_folder_did_not_exist = not os.path.isdir(app_data_path)
|
|
377
|
-
process_path(app_data_path, ensure_dir_exists=True)
|
|
452
|
+
# process_path(app_data_path, ensure_dir_exists=True)
|
|
378
453
|
|
|
379
454
|
if app_data_folder_did_not_exist:
|
|
380
455
|
setup_callback(app_data_path)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|