config2py 0.1.39__py3-none-any.whl → 0.1.40__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.
config2py/util.py CHANGED
@@ -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, Iterable
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
- # Note: First possible i2 dependency -- vendoring for now
278
- def get_app_rootdir(*, ensure_exists=True) -> str:
279
- """
280
- Returns the full path of a directory suitable for storing application-specific data.
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
- On Windows, this is typically %APPDATA%.
283
- On macOS, this is typically ~/.config.
284
- On Linux, this is typically ~/.config.
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
- See https://github.com/i2mint/i2mint/issues/1.
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
- >>> get_app_rootdir() # doctest: +SKIP
292
- '/Users/.../.config'
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
- >>> get_app_rootdir(ensure_exists=True) # doctest: +SKIP
298
- '/Users/.../.config'
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
- Note: The default app data folder is the system default for the current operating
301
- system. If you want to override this, you can do so by setting the
302
- CONFIG2PY_APP_DATA_FOLDER environment variable to the path of the folder you want
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
- return process_path(DFLT_APP_DATA_FOLDER, ensure_dir_exists=ensure_exists)
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
- - app_name (str): Name of the app for which the data directory is needed.
347
- - setup_callback (Callable[[str], None]): A callback function to initialize the directory.
348
- Default is _default_folder_setup.
349
- - ensure_exists (bool): Whether to ensure the directory exists.
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
- - str: Path to the app data directory.
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 though.
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
- >>> path = get_app_data_folder('my_app', ensure_exists=True) # doctest: +SKIP
364
- >>> path # doctest: +SKIP
365
- '/Users/.../.config/my_app'
366
- >>> os.path.exists(path) # doctest: +SKIP
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 data root directory
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/and/subfolder') # doctest: +SKIP
372
- '/Users/.../.config/another/app/and/subfolder'
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(get_app_rootdir(ensure_exists=ensure_exists), app_name)
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: config2py
3
- Version: 0.1.39
3
+ Version: 0.1.40
4
4
  Summary: Simplified reading and writing configurations from various sources and formats
5
5
  Home-page: https://github.com/i2mint/config2py
6
6
  License: apache-2.0
@@ -3,13 +3,13 @@ config2py/base.py,sha256=eQpRQjZYT-z6GhBemepaPUEyVVP8e_l04dghYeBBJdI,15880
3
3
  config2py/errors.py,sha256=QdwGsoJhv6LHDHp-_yyz4oUg1Fgu4S-S7O2nuA0a5cw,203
4
4
  config2py/s_configparser.py,sha256=-Sl2-J-QOLUiahwhCTiPsmjs4cKc79JuTbQ9gQcOiGY,15871
5
5
  config2py/tools.py,sha256=goDuHHXKJzdFgmHzDnLBGMZEhp0kKU-aK47c1-MpJT8,9199
6
- config2py/util.py,sha256=a3ynnasuO9DztfgAVmWwJfDVvfudKrWdFtJqxLIGbI8,17474
6
+ config2py/util.py,sha256=pnYRq7OnjOJfa3u0vhRXrleybJ6JCr39tByDsyB_qfA,20917
7
7
  config2py/scrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  config2py/tests/__init__.py,sha256=sk-yGJQOZES2z70M4xmZB57tsxSktX_84ybDuV8Cz5Q,297
9
9
  config2py/tests/test_tools.py,sha256=sdiBNTavuzxW2AsqBRTO9U21iWig5DEyV38r6lmaZak,3728
10
10
  config2py/tests/utils_for_testing.py,sha256=RcMiVtKK39rc8BsgIXQH3RCkd8qKo2o2MT7Rt0dJF2E,162
11
- config2py-0.1.39.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
12
- config2py-0.1.39.dist-info/METADATA,sha256=Uvu5Fe6r2JteTAlVeGJgTQaPd61daxtXOMmapyPYh90,14541
13
- config2py-0.1.39.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
14
- config2py-0.1.39.dist-info/top_level.txt,sha256=DFnlOIKMIGWQRROr3voJFhWFViHaWgTTeWZjC5YC9QQ,10
15
- config2py-0.1.39.dist-info/RECORD,,
11
+ config2py-0.1.40.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
12
+ config2py-0.1.40.dist-info/METADATA,sha256=5HPEIPtjVwB-QsYyNtgIRjACB4OKGPyrBAl48i-h_Ik,14541
13
+ config2py-0.1.40.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
14
+ config2py-0.1.40.dist-info/top_level.txt,sha256=DFnlOIKMIGWQRROr3voJFhWFViHaWgTTeWZjC5YC9QQ,10
15
+ config2py-0.1.40.dist-info/RECORD,,