quik-config


Namequik-config JSON
Version 1.9.1 PyPI version JSON
download
home_pagehttps://github.com/jeff-hykin/quik_config_python.git
SummaryProject config files
upload_time2024-07-01 20:35:42
maintainerNone
docs_urlNone
authorJeff Hykin
requires_python>=3.6
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # What is this?

A config system that doesn't waste your time
- all values in the hierarchy can be overridden with CLI args
- select multiple profiles from (ex: GPU & DEV or UNIX & GPU & PROD) from the CLI, profile settings get merged recursively
- configs supports secrets (unsynced file along side git-committed config)
- provides a consistent central way to handle filepaths
- profiles can inherit other profiles
- default works along side `argparse`, but also can replace it
- can combine/import multiple config files

# How do I use this?

`pip install quik-config`

In a `config.py`: 
```python
from quik_config import find_and_load

info = find_and_load(
    "info.yaml", # walks up folders until it finds a file with this name
    cd_to_filepath=True, # helpful if using relative paths
    fully_parse_args=True, # if you already have argparse, use parse_args=True instead
    show_help_for_no_args=False, # change if you want
)
# info.path_to
# info.absolute_path_to
# info.unused_args
# info.secrets
# info.available_profiles
# info.selected_profiles
# info.root_path
# info.project
# info.local_data
# info.as_dict
print(info.config) # dictionary
```

Create `info.yaml` with a structure like this:
```yaml
# names in parentheses are special, all other names are not!
# (e.g. add/extend this with any custom fields)
(project):
    # the local_data file will be auto-generated
    # (its for machine-specific data)
    # so probably git-ignore whatever path you pick
    (local_data): ./local_data.ignore.yaml
    
    # example profiles
    (profiles):
        (default):
            blah: "blah blah blah"
            mode: development # or production. Same thing really
            has_gpu: maybe
            constants:
                pi: 3 # its 'bout 3 
        
        PROFILE1:
            constants:
                e: 2.7182818285
        
        PROD:
            mode: production
            constants:
                pi: 3.1415926536
                problems: true
```

Then run it:
```shell
python ./config.py
```

Which will print out this config:
```py
{
    "blah": "blah blah blah", # from (default)
    "mode": "development",    # from (default)
    "has_gpu": "maybe",       # from (default)
    "constants": {
        "pi": 3,              # from (default)
    },
}
```

# Features

### Builtin Help

```shell
python ./config.py --help --profiles
```

```
available profiles:
    - DEV
    - GPU
    - PROD

as cli argument:
   -- --profiles='["DEV"]'
   -- --profiles='["GPU"]'
   -- --profiles='["PROD"]'
```


```
    ---------------------------------------------------------------------------------
    QUIK CONFIG HELP
    ---------------------------------------------------------------------------------
    
    open the file below and look for "(profiles)" for more information:
        $PWD/info.yaml
    
    examples:
        python3 ./ur_file.py   --  --help --profiles
        python3 ./ur_file.py   --  --help key1
        python3 ./ur_file.py   --  --help key1:subKey
        python3 ./ur_file.py   --  --help key1:subKey key2
        python3 ./ur_file.py   --  --profiles='[YOUR_PROFILE, YOUR_OTHER_PROFILE]'
        python3 ./ur_file.py   --  thing1:"Im a new value"          part2:"10000"
        python3 ./ur_file.py   --  thing1:"I : cause errors"        part2:10000
        python3 ./ur_file.py   --  'thing1:"I : dont cause errors"  part2:10000
        python3 ./ur_file.py   --  'thing1:["Im in a list"]'
        python3 ./ur_file.py   --  'thing1:part_A:"Im nested"'
        python3 ./ur_file.py "I get sent to ./ur_file.py" --  part2:"new value"
        python3 ./ur_file.py "I get ignored" "me too"  --  part2:10000
    
    how it works:
        - the "--" is a required argument, quik config only looks after the --
        - given "thing1:10", "thing1" is the key, "10" is the value
        - All values are parsed as json/yaml
            - "true" is boolean true
            - "10" is a number
            - '"10"' is a string (JSON escaping)
            - '"10\n"' is a string with a newline
            - '[10,11,hello]' is a list with two numbers and an unquoted string
            - '{"thing": 10}' is a map/object
            - "blah blah" is an un-quoted string with a space. Yes its valid YAML
            - multiline values are valid, you can dump an whole JSON doc as 1 arg
        - "thing1:10" overrides the "thing1" in the (profiles) of the info.yaml
        - "thing:subThing:10" is shorthand, 10 is the value, the others are keys
          it will only override the subThing (and will create it if necessary)
        - '{"thing": {"subThing":10} }' is long-hand for "thing:subThing:10"
        - '"thing:subThing":10' will currently not work for shorthand (parse error)
    
    options:
        --help
        --profiles
    
    ---------------------------------------------------------------------------------
    
    your default top-level keys:
        - mode
        - has_gpu
        - constants
    your local defaults file:
        ./local_data.ignore.yaml
    your default profiles:
        - DEV
    
    ---------------------------------------------------------------------------------

```


### Select Profiles from CLI

```shell
python ./config.py @PROFILE1
```

prints:
```py
{
    "blah": "blah blah blah", # from (default)
    "mode": "development",    # from (default)
    "has_gpu": "maybe",       # from (default)
    "constants": {
        "pi": 3.1415926536,   # from (default)
        "e": 2.7182818285,    # from PROFILE1
    },
}
```

```shell
python ./config.py @PROFILE1 @PROD
```

prints:
```py
{
    "blah": "blah blah blah", # from (default)
    "mode": "production",     # from PROD
    "has_gpu": "maybe",       # from (default)
    "constants": {
        "pi": 3.1415926536,   # from (default)
        "e": 2.7182818285,    # from PROFILE1
        "problems": True,     # from PROD
    },
}
```

### Override Values from CLI

```shell
python ./config.py @PROFILE1 mode:custom constants:problems:99
```

prints:
```py
{
    "blah": "blah blah blah", # from (default)
    "mode": "custom",         # from CLI
    "has_gpu": "maybe",       # from (default)
    "constants": {
        "pi": 3.1415926536,   # from (default)
        "e": 2.7182818285,    # from PROFILE1
        "problems": 99,       # from CLI
    },
}
```

Again but with really complicated arguments: <br>
(each argument is parsed as yaml)

```shell
python ./run.py arg1 --  mode:my_custom_mode  'constants: { tau: 6.2831853072, pi: 3.1415926, reserved_letters: [ "C", "K", "i" ] }'
```

prints:
```py
config: {
    "mode": "my_custom_mode", 
    "has_gpu": False, 
    "constants": {
        "pi": 3.1415926, 
        "tau": 6.2831853072, 
        "reserved_letters": ["C", "K", "i", ], 
    }, 
}
unused_args: ["arg1"]
```

### Working Alongside Argparse (quick)

Remove `fully_parse_args` and replace it with just `parse_args`

```py
info = find_and_load(
    "info.yaml",
    parse_args=True, # <- will only parse after -- 
)
```

Everthing in the CLI is the same, but it waits for `--`
For example:

```shell
# quik_config ignores arg1 --arg2 arg3, so argparse can do its thing with them
python ./config.py arg1 --arg2 arg3 -- @PROD
```

### Working Alongside Argparse (advanced)

Arguments can simply be passed as a list of strings, which can be useful for running many combinations of configs.

```py
info = find_and_load(
    "info.yaml",
    args=[ "@PROD" ],
)
```

### Relative and absolute paths

Add them to the info.yaml

```yaml
(project):
    (local_data): ./local_data.ignore.yaml
    
    # filepaths (relative to location of info.yaml)
    (path_to):
        this_file:       "./info.yaml"
        blah_file:       "./data/results.txt"
    
    # example profiles
    (profiles):
        (default):
            blah: "blah blah blah"
```

Access them in python
```py
info = find_and_load("info.yaml")
info.path_to.blah_file
info.absolute_path_to.blah_file # nice when then PWD != folder of the info file
```

### Import other yaml files

You can import multiple profiles by specifying profile sources.<br>
NOTE: the last profile source will override (merge keys) with the previous ones, but the main config will take the priority over any/all profile sources.

```yaml
(project):
    (profile_sources):
        - ./comments.yaml
        - ./camera_profiles.yaml
    
    (profiles):
        # you can also load a single profile as a file
        (GPU): !load_yaml_file ./profiles/gpu.yaml
```


## Different Profiles For Different Machines

Lets say you've several machines and an info.yaml like this:
```yaml
(project):
    (profiles):
        DEV:
            cores: 1
            database_ip: 192.168.10.10
            mode: dev
        LAPTOP:
            cores: 2
        DESKTOP:
            cores: 8
        UNIX:
            line_endings: "\n"
        WINDOWS:
            line_endings: "\r\n"
        PROD:
            database_ip: 183.177.10.83
            mode: prod
            cores: 32
```

And lets say you have a `config.py` like this:
```python
from quik_config import find_and_load
info = find_and_load(
    "info.yaml",
    defaults_for_local_data=["DEV", ],
    # if the ./local_data.ignore.yaml doesnt exist,
    # => create it and add DEV as the default no-argument choice
)
```

Run the code once to get a `./local_data.ignore.yaml` file. <br>

Each machine gets to pick the profiles it defaults to.<br>
So, on your Macbook you can edit the `./local_data.ignore.yaml` to include something like the following:
```yaml
(selected_profiles):
    - LAPTOP # the cores:2 will be used (instead of cores:1 from DEV)
    - UNIX   #     because LAPTOP is higher in the list than DEV
    - DEV
```

On your Windows laptop you can edit it and put:
```yaml
(selected_profiles):
    - LAPTOP
    - WINDOWS
    - DEV
```

## Command Line Arguments

If you have `run.py` like this:

```python
from quik_config import find_and_load

info = find_and_load("info.yaml", parse_args=True)

print("config:",      info.config     )
print("unused_args:", info.unused_args)

# 
# call some other function you've got
# 
#from your_code import run
#run(*info.unused_args)
```

### Example 0

Using the python file and config file above

```shell
python ./run.py
```

Running that will output:

```py
config: {
    "mode": "development",
    "has_gpu": False,
    "constants": {
        "pi": 3
    }
}
unused_args: []
```

### Example 1

Show help. This output can be overridden in the info.yaml by setting `(help):` under the `(project):` key.

```shell
python ./run.py -- --help
```

Note the `--` is needed in front of the help.

You can also add `show_help_for_no_args=True` if you want that behavior. <br>
Ex:

```python
from quik_config import find_and_load
info = find_and_load(
    "info.yaml",
    show_help_for_no_args=True
    parse_args=True,
)
```

### Example 2

Again but selecting some profiles

```shell
python ./run.py arg1 -- --profiles='[PROD]'
# or
python ./run.py arg1 -- @PROD
```

Output:

```py
config: {
    "mode": "production",
    "has_gpu": False,
    "constants": {
        "pi": 3.1415926536,
        "problems": True,
    },
}
unused_args: ["arg1"]
```

### Example 3

Again but with custom arguments:

```shell
python ./run.py arg1 --  mode:my_custom_mode  constants:tau:6.2831853072
```

```py
config: {
    "mode": "my_custom_mode",
    "has_gpu": False,
    "constants": {
        "pi": 3,
        "tau": 6.2831853072,
    },
}
unused_args: ["arg1"]
```

### Example 4

Again but with really complicated arguments: <br>
(each argument is parsed as yaml)

```shell
python ./run.py arg1 --  mode:my_custom_mode  'constants: { tau: 6.2831853072, pi: 3.1415926, reserved_letters: [ "C", "K", "i" ] }'
```

prints:

```py
config: {
    "mode": "my_custom_mode", 
    "has_gpu": False, 
    "constants": {
        "pi": 3.1415926, 
        "tau": 6.2831853072, 
        "reserved_letters": ["C", "K", "i", ], 
    }, 
}
unused_args: ["arg1"]
```

## Auto Generate Config-Specific Log Folders

If you add `path_from_config_to_log_folder="../logs",` as an argument to `find_and_load`
- The config is hashed
- A folder will be generated for each config
- A "run_index" is created (incrementes by 1 for each run with the same config)
- A nested folder will be created for each run
- `info.unique_run_path` is probably what you care about the most; the absolute path to log folder for this run
- `info.unique_config_path` is an absolute path to the unique config folder (summary stats for a specific config)

By default:
- config path is `./logs/[created_date]__[hash_of_config]/specific_config.yaml`
- run path is `./logs/[created_date]__[hash_of_config]/[run_index]__[date]__[git_commit]/`
    - run index is padded with zeros (ex: `0001`)
    - If you don't have git, the `__[git_commit]` won't be there
- The `./logs/[created_date]__[hash_of_config]/running_data.yaml` will look like this:
```yaml
# guaranteed one line per run, each line is JSON
- {"run_index": 0, "git_commit": "1a69c85b61dc52eac7b1edbe13dd78ebbe46ece5", "start_time": "2024-07-01T15:22:09.327494", "inital_run_name": "run/hi2_0000"}
- {"run_index": 1, "git_commit": "1a69c85b61dc52eac7b1edbe13dd78ebbe46ece5", "start_time": "2024-07-01T15:22:09.341325", "inital_run_name": "0001__2024-07-01--15-22__1a69c8"}
- {"run_index": 2, "git_commit": "1a69c85b61dc52eac7b1edbe13dd78ebbe46ece5", "start_time": "2024-07-01T15:22:09.541018", "inital_run_name": "0002__2024-07-01--15-22__1a69c8"}
```

You can customize almost everything:
- You can rename* any of the folders manually  (and it'll keep working)
    - *the config path NEEDS to end with the hash of the config, but thats the only constraint
    - Ex: `./logs/your_name[hash_of_config]/`
- padding of the run index (ex: `0001`) with `run_index_padding=4,`
- the length of the config hash (ex: `6`) with `config_hash_length=6,`
- the length of the git commit (ex: `6`) with `default_git_commit_length=6,`
- using `config_renamer`, `run_namer`, and `config_initial_namer` like so:

```py
from quik_config import find_and_load

config_renamer1 = lambda **_: "blahblah_"
config_renamer2 = lambda config, **_: f"{config.experiment_name}_"
config_renamer3 = lambda config, time, **_: f"{config.experiment_name}_{time}_"
config_renamer4 = lambda config, date, time, **_: f"{config.experiment_name}_{date}_{time}_"
config_renamer5 = lambda config, date, **_: f"{config.experiment_name}_{date}_"
config_renamer6 = lambda run_index, **_: f"{time}_{run_index}__"
# NOTE: run_index is a string, because its padded-out with zeros

run_namer1 = lambda **_: "blahblah"
run_namer2 = lambda run_index, **_: f"{run_index}"
run_namer3 = lambda run_index, **_: f"runs/{run_index}"
run_namer4 = lambda info, run_index, date, time, **_: f"runs_{date}/{run_index}"
# NOTE: for "run_namer", its okay to return "thing/{run_index}"
#       The sub-folders will be created

# only use this if you want to manually rename these folders
# (config_renamer would undo your manual rename)
config_initial_namer1 = lambda **_: "blahblah_"
config_initial_namer2 = lambda info, time, **_: f"0_{time}_"

# 
# basic example
# 
info = find_and_load(
    "info.yaml",
    path_from_config_to_log_folder="../logs",
    run_index_padding=4,
    config_hash_length=6,
    default_git_commit_length=6,
    config_renamer: lambda date, **_: f"{date}_",
    run_namer: lambda info, run_index, date, time, **_: f"{run_index}_{date}_{time}_",
    config_initial_namer: lambda info, time, **_: f"{time}_",
    config_renamer: lambda info, **_: f"blahblah_", # can "update" the name
)
print(info.this_run.run_index) # starts at 0 if this is a new/unique config
print(info.this_run.git_commit) # full 40-character git commit hash
print(info.this_run.start_time) # datetime.datetime.now() object (not a string)

print(info.unique_run_path)
print(info.unique_config_path)
print(info.log_folder)

# 
# lambda arg options
# 
    lambda date,                   **_: "blah"    # string: "2024-07-01"
    lambda time,                   **_: "blah"    # string: "17-52" (NOTE: "-" because colon is not valid filename character)
    lambda datetime,               **_: "blah"    # string: '2024-06-28--17-26'
    lambda now,                    **_: "blah"    # datetime.datetime object
    lambda config_hash,            **_: "blah"    # string: "lak4fa"
    lambda git_commit,             **_: "blah"    # 40-character git commit hash as a string
    lambda time_with_seconds,      **_: "blah"    # string: "17:52:00"
    lambda time_with_milliseconds, **_: "blah"    # string: "17:52:00.000"
    lambda year,                   **_: "blah"    # int
    lambda month,                  **_: "blah"    # int
    lambda day,                    **_: "blah"    # int
    lambda hour,                   **_: "blah"    # int
    lambda minute,                 **_: "blah"    # int
    lambda second,                 **_: "blah"    # int
    lambda unix_seconds,           **_: "blah"    # int
    lambda unix_milliseconds,      **_: "blah"    # int
```

<!-- 
TODO:
- make argument parsing not need the -- by default
- allow for multiple back to back runs with different args
 -->



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/jeff-hykin/quik_config_python.git",
    "name": "quik-config",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.6",
    "maintainer_email": null,
    "keywords": null,
    "author": "Jeff Hykin",
    "author_email": "jeff.hykin@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/d5/d1/a1b7e6b2d4ff57b5d994c888b922d61ce068d0b4485ae4ed552339480335/quik_config-1.9.1.tar.gz",
    "platform": null,
    "description": "# What is this?\n\nA config system that doesn't waste your time\n- all values in the hierarchy can be overridden with CLI args\n- select multiple profiles from (ex: GPU & DEV or UNIX & GPU & PROD) from the CLI, profile settings get merged recursively\n- configs supports secrets (unsynced file along side git-committed config)\n- provides a consistent central way to handle filepaths\n- profiles can inherit other profiles\n- default works along side `argparse`, but also can replace it\n- can combine/import multiple config files\n\n# How do I use this?\n\n`pip install quik-config`\n\nIn a `config.py`: \n```python\nfrom quik_config import find_and_load\n\ninfo = find_and_load(\n    \"info.yaml\", # walks up folders until it finds a file with this name\n    cd_to_filepath=True, # helpful if using relative paths\n    fully_parse_args=True, # if you already have argparse, use parse_args=True instead\n    show_help_for_no_args=False, # change if you want\n)\n# info.path_to\n# info.absolute_path_to\n# info.unused_args\n# info.secrets\n# info.available_profiles\n# info.selected_profiles\n# info.root_path\n# info.project\n# info.local_data\n# info.as_dict\nprint(info.config) # dictionary\n```\n\nCreate `info.yaml` with a structure like this:\n```yaml\n# names in parentheses are special, all other names are not!\n# (e.g. add/extend this with any custom fields)\n(project):\n    # the local_data file will be auto-generated\n    # (its for machine-specific data)\n    # so probably git-ignore whatever path you pick\n    (local_data): ./local_data.ignore.yaml\n    \n    # example profiles\n    (profiles):\n        (default):\n            blah: \"blah blah blah\"\n            mode: development # or production. Same thing really\n            has_gpu: maybe\n            constants:\n                pi: 3 # its 'bout 3 \n        \n        PROFILE1:\n            constants:\n                e: 2.7182818285\n        \n        PROD:\n            mode: production\n            constants:\n                pi: 3.1415926536\n                problems: true\n```\n\nThen run it:\n```shell\npython ./config.py\n```\n\nWhich will print out this config:\n```py\n{\n    \"blah\": \"blah blah blah\", # from (default)\n    \"mode\": \"development\",    # from (default)\n    \"has_gpu\": \"maybe\",       # from (default)\n    \"constants\": {\n        \"pi\": 3,              # from (default)\n    },\n}\n```\n\n# Features\n\n### Builtin Help\n\n```shell\npython ./config.py --help --profiles\n```\n\n```\navailable profiles:\n    - DEV\n    - GPU\n    - PROD\n\nas cli argument:\n   -- --profiles='[\"DEV\"]'\n   -- --profiles='[\"GPU\"]'\n   -- --profiles='[\"PROD\"]'\n```\n\n\n```\n    ---------------------------------------------------------------------------------\n    QUIK CONFIG HELP\n    ---------------------------------------------------------------------------------\n    \n    open the file below and look for \"(profiles)\" for more information:\n        $PWD/info.yaml\n    \n    examples:\n        python3 ./ur_file.py   --  --help --profiles\n        python3 ./ur_file.py   --  --help key1\n        python3 ./ur_file.py   --  --help key1:subKey\n        python3 ./ur_file.py   --  --help key1:subKey key2\n        python3 ./ur_file.py   --  --profiles='[YOUR_PROFILE, YOUR_OTHER_PROFILE]'\n        python3 ./ur_file.py   --  thing1:\"Im a new value\"          part2:\"10000\"\n        python3 ./ur_file.py   --  thing1:\"I : cause errors\"        part2:10000\n        python3 ./ur_file.py   --  'thing1:\"I : dont cause errors\"  part2:10000\n        python3 ./ur_file.py   --  'thing1:[\"Im in a list\"]'\n        python3 ./ur_file.py   --  'thing1:part_A:\"Im nested\"'\n        python3 ./ur_file.py \"I get sent to ./ur_file.py\" --  part2:\"new value\"\n        python3 ./ur_file.py \"I get ignored\" \"me too\"  --  part2:10000\n    \n    how it works:\n        - the \"--\" is a required argument, quik config only looks after the --\n        - given \"thing1:10\", \"thing1\" is the key, \"10\" is the value\n        - All values are parsed as json/yaml\n            - \"true\" is boolean true\n            - \"10\" is a number\n            - '\"10\"' is a string (JSON escaping)\n            - '\"10\\n\"' is a string with a newline\n            - '[10,11,hello]' is a list with two numbers and an unquoted string\n            - '{\"thing\": 10}' is a map/object\n            - \"blah blah\" is an un-quoted string with a space. Yes its valid YAML\n            - multiline values are valid, you can dump an whole JSON doc as 1 arg\n        - \"thing1:10\" overrides the \"thing1\" in the (profiles) of the info.yaml\n        - \"thing:subThing:10\" is shorthand, 10 is the value, the others are keys\n          it will only override the subThing (and will create it if necessary)\n        - '{\"thing\": {\"subThing\":10} }' is long-hand for \"thing:subThing:10\"\n        - '\"thing:subThing\":10' will currently not work for shorthand (parse error)\n    \n    options:\n        --help\n        --profiles\n    \n    ---------------------------------------------------------------------------------\n    \n    your default top-level keys:\n        - mode\n        - has_gpu\n        - constants\n    your local defaults file:\n        ./local_data.ignore.yaml\n    your default profiles:\n        - DEV\n    \n    ---------------------------------------------------------------------------------\n\n```\n\n\n### Select Profiles from CLI\n\n```shell\npython ./config.py @PROFILE1\n```\n\nprints:\n```py\n{\n    \"blah\": \"blah blah blah\", # from (default)\n    \"mode\": \"development\",    # from (default)\n    \"has_gpu\": \"maybe\",       # from (default)\n    \"constants\": {\n        \"pi\": 3.1415926536,   # from (default)\n        \"e\": 2.7182818285,    # from PROFILE1\n    },\n}\n```\n\n```shell\npython ./config.py @PROFILE1 @PROD\n```\n\nprints:\n```py\n{\n    \"blah\": \"blah blah blah\", # from (default)\n    \"mode\": \"production\",     # from PROD\n    \"has_gpu\": \"maybe\",       # from (default)\n    \"constants\": {\n        \"pi\": 3.1415926536,   # from (default)\n        \"e\": 2.7182818285,    # from PROFILE1\n        \"problems\": True,     # from PROD\n    },\n}\n```\n\n### Override Values from CLI\n\n```shell\npython ./config.py @PROFILE1 mode:custom constants:problems:99\n```\n\nprints:\n```py\n{\n    \"blah\": \"blah blah blah\", # from (default)\n    \"mode\": \"custom\",         # from CLI\n    \"has_gpu\": \"maybe\",       # from (default)\n    \"constants\": {\n        \"pi\": 3.1415926536,   # from (default)\n        \"e\": 2.7182818285,    # from PROFILE1\n        \"problems\": 99,       # from CLI\n    },\n}\n```\n\nAgain but with really complicated arguments: <br>\n(each argument is parsed as yaml)\n\n```shell\npython ./run.py arg1 --  mode:my_custom_mode  'constants: { tau: 6.2831853072, pi: 3.1415926, reserved_letters: [ \"C\", \"K\", \"i\" ] }'\n```\n\nprints:\n```py\nconfig: {\n    \"mode\": \"my_custom_mode\", \n    \"has_gpu\": False, \n    \"constants\": {\n        \"pi\": 3.1415926, \n        \"tau\": 6.2831853072, \n        \"reserved_letters\": [\"C\", \"K\", \"i\", ], \n    }, \n}\nunused_args: [\"arg1\"]\n```\n\n### Working Alongside Argparse (quick)\n\nRemove `fully_parse_args` and replace it with just `parse_args`\n\n```py\ninfo = find_and_load(\n    \"info.yaml\",\n    parse_args=True, # <- will only parse after -- \n)\n```\n\nEverthing in the CLI is the same, but it waits for `--`\nFor example:\n\n```shell\n# quik_config ignores arg1 --arg2 arg3, so argparse can do its thing with them\npython ./config.py arg1 --arg2 arg3 -- @PROD\n```\n\n### Working Alongside Argparse (advanced)\n\nArguments can simply be passed as a list of strings, which can be useful for running many combinations of configs.\n\n```py\ninfo = find_and_load(\n    \"info.yaml\",\n    args=[ \"@PROD\" ],\n)\n```\n\n### Relative and absolute paths\n\nAdd them to the info.yaml\n\n```yaml\n(project):\n    (local_data): ./local_data.ignore.yaml\n    \n    # filepaths (relative to location of info.yaml)\n    (path_to):\n        this_file:       \"./info.yaml\"\n        blah_file:       \"./data/results.txt\"\n    \n    # example profiles\n    (profiles):\n        (default):\n            blah: \"blah blah blah\"\n```\n\nAccess them in python\n```py\ninfo = find_and_load(\"info.yaml\")\ninfo.path_to.blah_file\ninfo.absolute_path_to.blah_file # nice when then PWD != folder of the info file\n```\n\n### Import other yaml files\n\nYou can import multiple profiles by specifying profile sources.<br>\nNOTE: the last profile source will override (merge keys) with the previous ones, but the main config will take the priority over any/all profile sources.\n\n```yaml\n(project):\n    (profile_sources):\n        - ./comments.yaml\n        - ./camera_profiles.yaml\n    \n    (profiles):\n        # you can also load a single profile as a file\n        (GPU): !load_yaml_file ./profiles/gpu.yaml\n```\n\n\n## Different Profiles For Different Machines\n\nLets say you've several machines and an info.yaml like this:\n```yaml\n(project):\n    (profiles):\n        DEV:\n            cores: 1\n            database_ip: 192.168.10.10\n            mode: dev\n        LAPTOP:\n            cores: 2\n        DESKTOP:\n            cores: 8\n        UNIX:\n            line_endings: \"\\n\"\n        WINDOWS:\n            line_endings: \"\\r\\n\"\n        PROD:\n            database_ip: 183.177.10.83\n            mode: prod\n            cores: 32\n```\n\nAnd lets say you have a `config.py` like this:\n```python\nfrom quik_config import find_and_load\ninfo = find_and_load(\n    \"info.yaml\",\n    defaults_for_local_data=[\"DEV\", ],\n    # if the ./local_data.ignore.yaml doesnt exist,\n    # => create it and add DEV as the default no-argument choice\n)\n```\n\nRun the code once to get a `./local_data.ignore.yaml` file. <br>\n\nEach machine gets to pick the profiles it defaults to.<br>\nSo, on your Macbook you can edit the `./local_data.ignore.yaml` to include something like the following:\n```yaml\n(selected_profiles):\n    - LAPTOP # the cores:2 will be used (instead of cores:1 from DEV)\n    - UNIX   #     because LAPTOP is higher in the list than DEV\n    - DEV\n```\n\nOn your Windows laptop you can edit it and put:\n```yaml\n(selected_profiles):\n    - LAPTOP\n    - WINDOWS\n    - DEV\n```\n\n## Command Line Arguments\n\nIf you have `run.py` like this:\n\n```python\nfrom quik_config import find_and_load\n\ninfo = find_and_load(\"info.yaml\", parse_args=True)\n\nprint(\"config:\",      info.config     )\nprint(\"unused_args:\", info.unused_args)\n\n# \n# call some other function you've got\n# \n#from your_code import run\n#run(*info.unused_args)\n```\n\n### Example 0\n\nUsing the python file and config file above\n\n```shell\npython ./run.py\n```\n\nRunning that will output:\n\n```py\nconfig: {\n    \"mode\": \"development\",\n    \"has_gpu\": False,\n    \"constants\": {\n        \"pi\": 3\n    }\n}\nunused_args: []\n```\n\n### Example 1\n\nShow help. This output can be overridden in the info.yaml by setting `(help):` under the `(project):` key.\n\n```shell\npython ./run.py -- --help\n```\n\nNote the `--` is needed in front of the help.\n\nYou can also add `show_help_for_no_args=True` if you want that behavior. <br>\nEx:\n\n```python\nfrom quik_config import find_and_load\ninfo = find_and_load(\n    \"info.yaml\",\n    show_help_for_no_args=True\n    parse_args=True,\n)\n```\n\n### Example 2\n\nAgain but selecting some profiles\n\n```shell\npython ./run.py arg1 -- --profiles='[PROD]'\n# or\npython ./run.py arg1 -- @PROD\n```\n\nOutput:\n\n```py\nconfig: {\n    \"mode\": \"production\",\n    \"has_gpu\": False,\n    \"constants\": {\n        \"pi\": 3.1415926536,\n        \"problems\": True,\n    },\n}\nunused_args: [\"arg1\"]\n```\n\n### Example 3\n\nAgain but with custom arguments:\n\n```shell\npython ./run.py arg1 --  mode:my_custom_mode  constants:tau:6.2831853072\n```\n\n```py\nconfig: {\n    \"mode\": \"my_custom_mode\",\n    \"has_gpu\": False,\n    \"constants\": {\n        \"pi\": 3,\n        \"tau\": 6.2831853072,\n    },\n}\nunused_args: [\"arg1\"]\n```\n\n### Example 4\n\nAgain but with really complicated arguments: <br>\n(each argument is parsed as yaml)\n\n```shell\npython ./run.py arg1 --  mode:my_custom_mode  'constants: { tau: 6.2831853072, pi: 3.1415926, reserved_letters: [ \"C\", \"K\", \"i\" ] }'\n```\n\nprints:\n\n```py\nconfig: {\n    \"mode\": \"my_custom_mode\", \n    \"has_gpu\": False, \n    \"constants\": {\n        \"pi\": 3.1415926, \n        \"tau\": 6.2831853072, \n        \"reserved_letters\": [\"C\", \"K\", \"i\", ], \n    }, \n}\nunused_args: [\"arg1\"]\n```\n\n## Auto Generate Config-Specific Log Folders\n\nIf you add `path_from_config_to_log_folder=\"../logs\",` as an argument to `find_and_load`\n- The config is hashed\n- A folder will be generated for each config\n- A \"run_index\" is created (incrementes by 1 for each run with the same config)\n- A nested folder will be created for each run\n- `info.unique_run_path` is probably what you care about the most; the absolute path to log folder for this run\n- `info.unique_config_path` is an absolute path to the unique config folder (summary stats for a specific config)\n\nBy default:\n- config path is `./logs/[created_date]__[hash_of_config]/specific_config.yaml`\n- run path is `./logs/[created_date]__[hash_of_config]/[run_index]__[date]__[git_commit]/`\n    - run index is padded with zeros (ex: `0001`)\n    - If you don't have git, the `__[git_commit]` won't be there\n- The `./logs/[created_date]__[hash_of_config]/running_data.yaml` will look like this:\n```yaml\n# guaranteed one line per run, each line is JSON\n- {\"run_index\": 0, \"git_commit\": \"1a69c85b61dc52eac7b1edbe13dd78ebbe46ece5\", \"start_time\": \"2024-07-01T15:22:09.327494\", \"inital_run_name\": \"run/hi2_0000\"}\n- {\"run_index\": 1, \"git_commit\": \"1a69c85b61dc52eac7b1edbe13dd78ebbe46ece5\", \"start_time\": \"2024-07-01T15:22:09.341325\", \"inital_run_name\": \"0001__2024-07-01--15-22__1a69c8\"}\n- {\"run_index\": 2, \"git_commit\": \"1a69c85b61dc52eac7b1edbe13dd78ebbe46ece5\", \"start_time\": \"2024-07-01T15:22:09.541018\", \"inital_run_name\": \"0002__2024-07-01--15-22__1a69c8\"}\n```\n\nYou can customize almost everything:\n- You can rename* any of the folders manually\u00a0 (and it'll keep working)\n    - *the config path NEEDS to end with the hash of the config, but thats the only constraint\n    - Ex: `./logs/your_name[hash_of_config]/`\n- padding of the run index (ex: `0001`) with `run_index_padding=4,`\n- the length of the config hash (ex: `6`) with `config_hash_length=6,`\n- the length of the git commit (ex: `6`) with `default_git_commit_length=6,`\n- using `config_renamer`, `run_namer`, and `config_initial_namer` like so:\n\n```py\nfrom quik_config import find_and_load\n\nconfig_renamer1 = lambda **_: \"blahblah_\"\nconfig_renamer2 = lambda config, **_: f\"{config.experiment_name}_\"\nconfig_renamer3 = lambda config, time, **_: f\"{config.experiment_name}_{time}_\"\nconfig_renamer4 = lambda config, date, time, **_: f\"{config.experiment_name}_{date}_{time}_\"\nconfig_renamer5 = lambda config, date, **_: f\"{config.experiment_name}_{date}_\"\nconfig_renamer6 = lambda run_index, **_: f\"{time}_{run_index}__\"\n# NOTE: run_index is a string, because its padded-out with zeros\n\nrun_namer1 = lambda **_: \"blahblah\"\nrun_namer2 = lambda run_index, **_: f\"{run_index}\"\nrun_namer3 = lambda run_index, **_: f\"runs/{run_index}\"\nrun_namer4 = lambda info, run_index, date, time, **_: f\"runs_{date}/{run_index}\"\n# NOTE: for \"run_namer\", its okay to return \"thing/{run_index}\"\n#       The sub-folders will be created\n\n# only use this if you want to manually rename these folders\n# (config_renamer would undo your manual rename)\nconfig_initial_namer1 = lambda **_: \"blahblah_\"\nconfig_initial_namer2 = lambda info, time, **_: f\"0_{time}_\"\n\n# \n# basic example\n# \ninfo = find_and_load(\n    \"info.yaml\",\n    path_from_config_to_log_folder=\"../logs\",\n    run_index_padding=4,\n    config_hash_length=6,\n    default_git_commit_length=6,\n    config_renamer: lambda date, **_: f\"{date}_\",\n    run_namer: lambda info, run_index, date, time, **_: f\"{run_index}_{date}_{time}_\",\n    config_initial_namer: lambda info, time, **_: f\"{time}_\",\n    config_renamer: lambda info, **_: f\"blahblah_\", # can \"update\" the name\n)\nprint(info.this_run.run_index) # starts at 0 if this is a new/unique config\nprint(info.this_run.git_commit) # full 40-character git commit hash\nprint(info.this_run.start_time) # datetime.datetime.now() object (not a string)\n\nprint(info.unique_run_path)\nprint(info.unique_config_path)\nprint(info.log_folder)\n\n# \n# lambda arg options\n# \n    lambda date,                   **_: \"blah\"    # string: \"2024-07-01\"\n    lambda time,                   **_: \"blah\"    # string: \"17-52\" (NOTE: \"-\" because colon is not valid filename character)\n    lambda datetime,               **_: \"blah\"    # string: '2024-06-28--17-26'\n    lambda now,                    **_: \"blah\"    # datetime.datetime object\n    lambda config_hash,            **_: \"blah\"    # string: \"lak4fa\"\n    lambda git_commit,             **_: \"blah\"    # 40-character git commit hash as a string\n    lambda time_with_seconds,      **_: \"blah\"    # string: \"17:52:00\"\n    lambda time_with_milliseconds, **_: \"blah\"    # string: \"17:52:00.000\"\n    lambda year,                   **_: \"blah\"    # int\n    lambda month,                  **_: \"blah\"    # int\n    lambda day,                    **_: \"blah\"    # int\n    lambda hour,                   **_: \"blah\"    # int\n    lambda minute,                 **_: \"blah\"    # int\n    lambda second,                 **_: \"blah\"    # int\n    lambda unix_seconds,           **_: \"blah\"    # int\n    lambda unix_milliseconds,      **_: \"blah\"    # int\n```\n\n<!-- \nTODO:\n- make argument parsing not need the -- by default\n- allow for multiple back to back runs with different args\n -->\n\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Project config files",
    "version": "1.9.1",
    "project_urls": {
        "Homepage": "https://github.com/jeff-hykin/quik_config_python.git"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c63d8c9be4814451ce8de91828aaf89d2410ea48a2f7ec5de1f8d87969fedb40",
                "md5": "4f457dbbb61dc940ab717ade28c014c6",
                "sha256": "46508a7abbec10e097028340e7e67c3264d8dc9699ef299f8f54ec7948779201"
            },
            "downloads": -1,
            "filename": "quik_config-1.9.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "4f457dbbb61dc940ab717ade28c014c6",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.6",
            "size": 4679539,
            "upload_time": "2024-07-01T20:35:40",
            "upload_time_iso_8601": "2024-07-01T20:35:40.233575Z",
            "url": "https://files.pythonhosted.org/packages/c6/3d/8c9be4814451ce8de91828aaf89d2410ea48a2f7ec5de1f8d87969fedb40/quik_config-1.9.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d5d1a1b7e6b2d4ff57b5d994c888b922d61ce068d0b4485ae4ed552339480335",
                "md5": "f4c7bab925411ef66a959d53c8dbf081",
                "sha256": "f82f0717667cd2f1ffcd556b0ea3cd942b23c5fc1b7b21c9e01e025d6ae05601"
            },
            "downloads": -1,
            "filename": "quik_config-1.9.1.tar.gz",
            "has_sig": false,
            "md5_digest": "f4c7bab925411ef66a959d53c8dbf081",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6",
            "size": 4955559,
            "upload_time": "2024-07-01T20:35:42",
            "upload_time_iso_8601": "2024-07-01T20:35:42.649111Z",
            "url": "https://files.pythonhosted.org/packages/d5/d1/a1b7e6b2d4ff57b5d994c888b922d61ce068d0b4485ae4ed552339480335/quik_config-1.9.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-07-01 20:35:42",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "jeff-hykin",
    "github_project": "quik_config_python",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "quik-config"
}
        
Elapsed time: 0.51365s