sws-config


Namesws-config JSON
Version 0.3.2 PyPI version JSON
download
home_pageNone
SummaryMinimal pythonic config library for deep learning experiments
upload_time2025-08-27 19:09:59
maintainerNone
docs_urlNone
authorLucas Beyer, OpenAI Assistant
requires_python>=3.9
licenseMIT License Copyright (c) 2025 Lucas Beyer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords cli config configuration deep learning experiments
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # sws

[![Tests](https://github.com/lucasb-eyer/sws/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/lucasb-eyer/sws/actions/workflows/tests.yml)
[![codecov](https://codecov.io/gh/lucasb-eyer/sws/branch/master/graph/badge.svg)](https://codecov.io/gh/lucasb-eyer/sws)

Minimal, predictable, footgun-free configuration for deep learning experiments.
The most similar existing ones are
[OmegaConf](https://omegaconf.readthedocs.io/en/2.3_branch/usage.html) and
[ConfigDict](https://github.com/google/ml_collections) -
if you are happy with them, you probably don't need this.
If you want some lore, have a look at the end.

The remainder of this readme follows the CODE THEN EXPLAIN layout.
The `example/` folder contains a nearly real-world example of structuring a project.
Install instructions at the end.

## Basics

```python
from sws import Config

# Create the config and populate the fields with defaults
c = Config()
c.lr = 3e-4

# Alternative shorthand handy for very small configs:
c = Config(lr=3e-4)

# How to make a field depend on others?
c.wd = c.lr * 0.1  # ERROR: c is write-only.
# Instead, use a lambda to make the value "lazy"
c.wd = lambda: c.lr * 0.1

# Finalizing resolves all fields to plain values, and integrates CLI args:
c = c.finalize(argv=sys.argv[1:])
assert c.lr == 3e-4 and c.wd == 3e-5

train_agi(lr=c.lr, wd=c.wd)
```

`sws` clearly separates two phases: config creation, and config use.
At creation time, you build a (possibly nested) `Config` object.
To avoid subtle bugs common in many config libraries I've used before, at
creation time, the `Config` object is *write-only*; you cannot read its values.
Once you finished building it up, a call to `c.finalize()` turns it into a
read-only `FinalConfig` object that contains "final" values for all fields.

This *finalization* step can also integrate overrides from, for example,
commandline arguments; more on that a little later.

If you want to make one field's value depend on another field's value, you can
do so by wrapping the value in a `lambda`, which computes the derived value.
This lambda will be called during finalization, where concrete config values
can be accessed. In this way, in the example above, the `wd` setting will use
the correct value of `c.lr` even when it is overriden by commandline arguments
during `finalize`. This works transitively, just as you'd expect it to.

Since callable values receive this special treatment, if you want to actually
set a config field's value to an actual function, that needs to be wrapped by
`sws.Fn`:

```python
from sws import Fn

# If you want to store a callable as a value (not execute it at finalize), wrap it:
c.log_fn = Fn(lambda s: print(s))
c = c.finalize()

# Five moments later...
c.log_fn("After finalization, the config field is just this plain function")
```

## Nesting

Of course any respectable config library allows nested structures:

```python
from sws import Config

# Create the config and populate the fields with defaults
c = Config()
c.lr = 3e-4
c.model.depth = 4  # No need to create parents first.

# In a nested field, lazy and `c` work just as you'd expect them to:
c.model.width = lambda: c.model.depth * 64
c.model.emb_lr = lambda: c.lr * 10 / c.model.width

c = c.finalize()

# Pass model settings as kwargs, for example:
m = MyAGIModel(**c.model.to_dict())
train_agi(m, c.lr)
```

The reason we need `to_dict()` above is that `FinalConfig` implements as few methods as possible,
to leave as many names as possible free to be used for configs. For instance, `keys`, `values`, and
`items` are not implemented so that you can use them as config names.
This also means, that it doesn't implement the `Mapping` protocol and can't be `**`'ed.
So, just call `to_dict`, it's fine.

You don't really need to know this, but internally, the full config is stored as a flat dict
(`"model.emb_lr"` is a key), and subfields are just prefix-views into that dict.

## Commandline overrides

The `finalize()` method allows you to pass a list of `argv` strings to it that serve as overrides:

```python
from sws import Config

c = Config(lr=1.0, model={"width": 128, "depth": 4})
c = c.finalize(["c.model.width=512", "c.model.depth=2+2"])

# However, we're lazy. The shortest unique segment suffix works:
c = c.finalize(["width=512", "depth=2+2"])

# In real life, you'd probably pass sys.argv[1:] instead.
```

Only the syntax `a=b` is supported, any argument without `=` is ignored.
This is to reduce ambiguity and allow catching typos.

The values of the overrides are parsed as Python expressions using the `simpleeval`
library. This makes a lot of Python code just work, for example you can write
`model.vocab=[ord(c) for c in "hello"]` and it'll work. You can also access the
current config using the name `c`, so something like `'c.model.width=3 * c.model.depth'`
works. Note that I quoted the whole thing, for two reasons: (1) to stop my shell
from interpreting `*` as wildcard, and (2) because I used spaces.

For convenience, the keyname can be shortened to the shortest unique suffix
across the _whole_ config (i.e. all nesting levels).
For example, `model.head.lr` can be shortened to `head.lr` or `lr` if unambiguous.

## `sws.run` and suggested code structure

The `train.py` file could look something like this:

```python
import sws

# ...lots of code...

def train(c):
    # Do some AGI things, but be careful please.
    # `c` is a FinalConfig here, i.e. it's been finalized.

if __name__ == "__main__":
    sws.run(train)
```

This seemingly innocuous code does a lot, thanks to judiciously chosen default arguments.
The full call would be `sws.run(train, argv=sys.argv[1:], config_flag="--config", default_func="get_config")`.

First, it looks for a commandline argument `--config filename.py` (or `--config=filename.py`).

It then loads said file, and runs the `get_config` function defined therein,
which should return a fully populated `sws.Config` object. Note that it's plain
python code, so it may import things, have a lot of logic, feel free to do as much
or as little as you want.

Finally, it `finalize`s the config with the remaining commandline arguments,
and calls the specified function (in this example, `train`) with the `FinalConfig`.

Here's what a config file might look like, let's call it `vit_i1k.py`:

```python
from sws import Config

def get_config():
    c = Config()
    c.lr = 3e-4
    c.wd = lambda: c.lr * 0.1
    c.model.name = "vit"
    c.model.depth = 8
    c.model.width = 512
    c.model.patch_size = (16, 16)
    c.dataset = "imagenet_2012"
    c.batch = 4096
    return c
```

Then, you would run training as `python -m train --config vit_i1k.py batch=1024`.
In a real codebase, you'd have quite a few config files, maybe in some structured
`config/` folder with sub-folders per project, user, topic, ...

There's three more things `sws.run` does for convenience:
- If no `--config` is passed, it looks for the `get_config` function in the file
  which called it. This is very convenient for quick small scripts.
- If you use `run(fn, forward_extras=True)`, then all unused commandline arguments,
  i.e. all those without a `=`, are passed in a list as the second argument to `fn`.
  This can be used to do further custom processing unrelated to `sws`.
- For extra flexibility, you can actually specify which function should be called.
  The syntax is `--config file.py:function_name`, it's just that the function name
  defaults to `get_config`. This way, you can have multiple slight variants in the
  same file, for example.

See the `example/` folder of this repo for a semi-realistic example, including
a sweep to run sweeps.

## A realistic example

This is how I'd structure a codebase, roughly. See also `example/` folder.

Various experiment configurations in the `configs/` folder. For example, `configs/super_agi.py`:

```python
from sws import Config

def get_config():
    c = Config()
    c.lr = 0.001
    c.wd = lambda: c.lr * 0.1
    c.model.depth = 4
    c.model.width = 256
    c.model.heads = lambda: 4 if c.model.width > 128 else 1
    return c
```

Your main code, for example `train.py`:

```python
from sws import run

def main(c):
    print("Training with config:\n" + str(c))
    # Your training code here...

if __name__ == "__main__":
    run(main)
```

Run a different config file and override values from CLI if wanted:

```bash
python -m train --config configs/super_agi.py model.depth=32
```

See `example/sweep.fish` for a trivial sweep over a few values.

## Some more misc notes

- The `FinalConfig` has a nice pretty printer when cast to string or printed.
- When a dict is assigned to a `Config` field, it's turned into a `Config`.
- You cannot set a group to a value or vice-versa, i.e. no `c.model = "vit"`
  followed by `c.model.depth = 4` or vice-versa.
- Cycles in computed callables are detected and raise an exception at `finalize`.

# Installing
```bash
pip install sws-config
```

# Testing
```bash
python -m pytest
```

# TODOs

- When passing commandline args, using lazy/lambda makes no more sense.
  So we should lift the requirement for `Fn`-wrapping of callables here.
  `'log_fn=Fn(lambda s: print(f"Log: {s}"))'`.
- finalization no more converts collections into tuples, sets into frozensets,
  and expands dicts. If a lazy field returns a dict, it's just a dict.
  Consider if this is good or bad, though it does sound obscure.

Probably overkill:
- Auto-generate a commandline --help?
- Auto-generate a terminal UI to browse/change config values on `finalize()` could be fun.
- Rewrite the finalization algorithm. It currently is obviously vibe-coded shit;
  it's correct, but way over-complicated and could be much simpler.

# Lore

You obviously wonder "Why yet another config library, ffs?!" - and you're right.
There are many, but there's none that fully pleases me. So [I gave in](https://x.com/giffmana/status/1953200176526471637).

I've heavily used, and hence been influenced by, many config systems in the past.
Most notably [`ml_collections.ConfigDict`](https://github.com/google/ml_collections)
and [`chz`](https://github.com/openai/chz), both of which I generally liked,
but both had quite some pitfalls after serious use, which I try to avoid here.
Notable examples which I used but _did not_ like are [`gin`](https://github.com/google/gin-config),
[yaml](https://en.wikipedia.org/wiki/YAML) / [Hydra](https://hydra.cc/docs/intro/),
[`kauldron.konfig`](https://kauldron.readthedocs.io/en/latest/konfig_philosophy.html);
they are too heavy, unpythonic, and magic; there be footguns.
[fiddle](https://github.com/google/fiddle) requires your config to import everything,
which I don't like.
I refuse to built around types in Python, like pydantic, tyro, dataclasses, ..., so not even linking them.
Finally, I haven't used, but thoroughly read [Pydra](https://github.com/jordan-benjamin/pydra)
and [Cue](https://cuelang.org/docs/tour/), which together inspired the two-step
approach with finalization.

Why is it called `sws`? It's a nod to OpenAI's `chz` config library,
and the author being a very fond resident of Switzerland.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "sws-config",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "cli, config, configuration, deep learning, experiments",
    "author": "Lucas Beyer, OpenAI Assistant",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/d6/79/8d439872f04012aa4ff71255ce96c86a327109c81a35e335660bc1381547/sws_config-0.3.2.tar.gz",
    "platform": null,
    "description": "# sws\n\n[![Tests](https://github.com/lucasb-eyer/sws/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/lucasb-eyer/sws/actions/workflows/tests.yml)\n[![codecov](https://codecov.io/gh/lucasb-eyer/sws/branch/master/graph/badge.svg)](https://codecov.io/gh/lucasb-eyer/sws)\n\nMinimal, predictable, footgun-free configuration for deep learning experiments.\nThe most similar existing ones are\n[OmegaConf](https://omegaconf.readthedocs.io/en/2.3_branch/usage.html) and\n[ConfigDict](https://github.com/google/ml_collections) -\nif you are happy with them, you probably don't need this.\nIf you want some lore, have a look at the end.\n\nThe remainder of this readme follows the CODE THEN EXPLAIN layout.\nThe `example/` folder contains a nearly real-world example of structuring a project.\nInstall instructions at the end.\n\n## Basics\n\n```python\nfrom sws import Config\n\n# Create the config and populate the fields with defaults\nc = Config()\nc.lr = 3e-4\n\n# Alternative shorthand handy for very small configs:\nc = Config(lr=3e-4)\n\n# How to make a field depend on others?\nc.wd = c.lr * 0.1  # ERROR: c is write-only.\n# Instead, use a lambda to make the value \"lazy\"\nc.wd = lambda: c.lr * 0.1\n\n# Finalizing resolves all fields to plain values, and integrates CLI args:\nc = c.finalize(argv=sys.argv[1:])\nassert c.lr == 3e-4 and c.wd == 3e-5\n\ntrain_agi(lr=c.lr, wd=c.wd)\n```\n\n`sws` clearly separates two phases: config creation, and config use.\nAt creation time, you build a (possibly nested) `Config` object.\nTo avoid subtle bugs common in many config libraries I've used before, at\ncreation time, the `Config` object is *write-only*; you cannot read its values.\nOnce you finished building it up, a call to `c.finalize()` turns it into a\nread-only `FinalConfig` object that contains \"final\" values for all fields.\n\nThis *finalization* step can also integrate overrides from, for example,\ncommandline arguments; more on that a little later.\n\nIf you want to make one field's value depend on another field's value, you can\ndo so by wrapping the value in a `lambda`, which computes the derived value.\nThis lambda will be called during finalization, where concrete config values\ncan be accessed. In this way, in the example above, the `wd` setting will use\nthe correct value of `c.lr` even when it is overriden by commandline arguments\nduring `finalize`. This works transitively, just as you'd expect it to.\n\nSince callable values receive this special treatment, if you want to actually\nset a config field's value to an actual function, that needs to be wrapped by\n`sws.Fn`:\n\n```python\nfrom sws import Fn\n\n# If you want to store a callable as a value (not execute it at finalize), wrap it:\nc.log_fn = Fn(lambda s: print(s))\nc = c.finalize()\n\n# Five moments later...\nc.log_fn(\"After finalization, the config field is just this plain function\")\n```\n\n## Nesting\n\nOf course any respectable config library allows nested structures:\n\n```python\nfrom sws import Config\n\n# Create the config and populate the fields with defaults\nc = Config()\nc.lr = 3e-4\nc.model.depth = 4  # No need to create parents first.\n\n# In a nested field, lazy and `c` work just as you'd expect them to:\nc.model.width = lambda: c.model.depth * 64\nc.model.emb_lr = lambda: c.lr * 10 / c.model.width\n\nc = c.finalize()\n\n# Pass model settings as kwargs, for example:\nm = MyAGIModel(**c.model.to_dict())\ntrain_agi(m, c.lr)\n```\n\nThe reason we need `to_dict()` above is that `FinalConfig` implements as few methods as possible,\nto leave as many names as possible free to be used for configs. For instance, `keys`, `values`, and\n`items` are not implemented so that you can use them as config names.\nThis also means, that it doesn't implement the `Mapping` protocol and can't be `**`'ed.\nSo, just call `to_dict`, it's fine.\n\nYou don't really need to know this, but internally, the full config is stored as a flat dict\n(`\"model.emb_lr\"` is a key), and subfields are just prefix-views into that dict.\n\n## Commandline overrides\n\nThe `finalize()` method allows you to pass a list of `argv` strings to it that serve as overrides:\n\n```python\nfrom sws import Config\n\nc = Config(lr=1.0, model={\"width\": 128, \"depth\": 4})\nc = c.finalize([\"c.model.width=512\", \"c.model.depth=2+2\"])\n\n# However, we're lazy. The shortest unique segment suffix works:\nc = c.finalize([\"width=512\", \"depth=2+2\"])\n\n# In real life, you'd probably pass sys.argv[1:] instead.\n```\n\nOnly the syntax `a=b` is supported, any argument without `=` is ignored.\nThis is to reduce ambiguity and allow catching typos.\n\nThe values of the overrides are parsed as Python expressions using the `simpleeval`\nlibrary. This makes a lot of Python code just work, for example you can write\n`model.vocab=[ord(c) for c in \"hello\"]` and it'll work. You can also access the\ncurrent config using the name `c`, so something like `'c.model.width=3 * c.model.depth'`\nworks. Note that I quoted the whole thing, for two reasons: (1) to stop my shell\nfrom interpreting `*` as wildcard, and (2) because I used spaces.\n\nFor convenience, the keyname can be shortened to the shortest unique suffix\nacross the _whole_ config (i.e. all nesting levels).\nFor example, `model.head.lr` can be shortened to `head.lr` or `lr` if unambiguous.\n\n## `sws.run` and suggested code structure\n\nThe `train.py` file could look something like this:\n\n```python\nimport sws\n\n# ...lots of code...\n\ndef train(c):\n    # Do some AGI things, but be careful please.\n    # `c` is a FinalConfig here, i.e. it's been finalized.\n\nif __name__ == \"__main__\":\n    sws.run(train)\n```\n\nThis seemingly innocuous code does a lot, thanks to judiciously chosen default arguments.\nThe full call would be `sws.run(train, argv=sys.argv[1:], config_flag=\"--config\", default_func=\"get_config\")`.\n\nFirst, it looks for a commandline argument `--config filename.py` (or `--config=filename.py`).\n\nIt then loads said file, and runs the `get_config` function defined therein,\nwhich should return a fully populated `sws.Config` object. Note that it's plain\npython code, so it may import things, have a lot of logic, feel free to do as much\nor as little as you want.\n\nFinally, it `finalize`s the config with the remaining commandline arguments,\nand calls the specified function (in this example, `train`) with the `FinalConfig`.\n\nHere's what a config file might look like, let's call it `vit_i1k.py`:\n\n```python\nfrom sws import Config\n\ndef get_config():\n    c = Config()\n    c.lr = 3e-4\n    c.wd = lambda: c.lr * 0.1\n    c.model.name = \"vit\"\n    c.model.depth = 8\n    c.model.width = 512\n    c.model.patch_size = (16, 16)\n    c.dataset = \"imagenet_2012\"\n    c.batch = 4096\n    return c\n```\n\nThen, you would run training as `python -m train --config vit_i1k.py batch=1024`.\nIn a real codebase, you'd have quite a few config files, maybe in some structured\n`config/` folder with sub-folders per project, user, topic, ...\n\nThere's three more things `sws.run` does for convenience:\n- If no `--config` is passed, it looks for the `get_config` function in the file\n  which called it. This is very convenient for quick small scripts.\n- If you use `run(fn, forward_extras=True)`, then all unused commandline arguments,\n  i.e. all those without a `=`, are passed in a list as the second argument to `fn`.\n  This can be used to do further custom processing unrelated to `sws`.\n- For extra flexibility, you can actually specify which function should be called.\n  The syntax is `--config file.py:function_name`, it's just that the function name\n  defaults to `get_config`. This way, you can have multiple slight variants in the\n  same file, for example.\n\nSee the `example/` folder of this repo for a semi-realistic example, including\na sweep to run sweeps.\n\n## A realistic example\n\nThis is how I'd structure a codebase, roughly. See also `example/` folder.\n\nVarious experiment configurations in the `configs/` folder. For example, `configs/super_agi.py`:\n\n```python\nfrom sws import Config\n\ndef get_config():\n    c = Config()\n    c.lr = 0.001\n    c.wd = lambda: c.lr * 0.1\n    c.model.depth = 4\n    c.model.width = 256\n    c.model.heads = lambda: 4 if c.model.width > 128 else 1\n    return c\n```\n\nYour main code, for example `train.py`:\n\n```python\nfrom sws import run\n\ndef main(c):\n    print(\"Training with config:\\n\" + str(c))\n    # Your training code here...\n\nif __name__ == \"__main__\":\n    run(main)\n```\n\nRun a different config file and override values from CLI if wanted:\n\n```bash\npython -m train --config configs/super_agi.py model.depth=32\n```\n\nSee `example/sweep.fish` for a trivial sweep over a few values.\n\n## Some more misc notes\n\n- The `FinalConfig` has a nice pretty printer when cast to string or printed.\n- When a dict is assigned to a `Config` field, it's turned into a `Config`.\n- You cannot set a group to a value or vice-versa, i.e. no `c.model = \"vit\"`\n  followed by `c.model.depth = 4` or vice-versa.\n- Cycles in computed callables are detected and raise an exception at `finalize`.\n\n# Installing\n```bash\npip install sws-config\n```\n\n# Testing\n```bash\npython -m pytest\n```\n\n# TODOs\n\n- When passing commandline args, using lazy/lambda makes no more sense.\n  So we should lift the requirement for `Fn`-wrapping of callables here.\n  `'log_fn=Fn(lambda s: print(f\"Log: {s}\"))'`.\n- finalization no more converts collections into tuples, sets into frozensets,\n  and expands dicts. If a lazy field returns a dict, it's just a dict.\n  Consider if this is good or bad, though it does sound obscure.\n\nProbably overkill:\n- Auto-generate a commandline --help?\n- Auto-generate a terminal UI to browse/change config values on `finalize()` could be fun.\n- Rewrite the finalization algorithm. It currently is obviously vibe-coded shit;\n  it's correct, but way over-complicated and could be much simpler.\n\n# Lore\n\nYou obviously wonder \"Why yet another config library, ffs?!\" - and you're right.\nThere are many, but there's none that fully pleases me. So [I gave in](https://x.com/giffmana/status/1953200176526471637).\n\nI've heavily used, and hence been influenced by, many config systems in the past.\nMost notably [`ml_collections.ConfigDict`](https://github.com/google/ml_collections)\nand [`chz`](https://github.com/openai/chz), both of which I generally liked,\nbut both had quite some pitfalls after serious use, which I try to avoid here.\nNotable examples which I used but _did not_ like are [`gin`](https://github.com/google/gin-config),\n[yaml](https://en.wikipedia.org/wiki/YAML) / [Hydra](https://hydra.cc/docs/intro/),\n[`kauldron.konfig`](https://kauldron.readthedocs.io/en/latest/konfig_philosophy.html);\nthey are too heavy, unpythonic, and magic; there be footguns.\n[fiddle](https://github.com/google/fiddle) requires your config to import everything,\nwhich I don't like.\nI refuse to built around types in Python, like pydantic, tyro, dataclasses, ..., so not even linking them.\nFinally, I haven't used, but thoroughly read [Pydra](https://github.com/jordan-benjamin/pydra)\nand [Cue](https://cuelang.org/docs/tour/), which together inspired the two-step\napproach with finalization.\n\nWhy is it called `sws`? It's a nod to OpenAI's `chz` config library,\nand the author being a very fond resident of Switzerland.\n",
    "bugtrack_url": null,
    "license": "MIT License\n        \n        Copyright (c) 2025 Lucas Beyer\n        \n        Permission is hereby granted, free of charge, to any person obtaining a copy\n        of this software and associated documentation files (the \"Software\"), to deal\n        in the Software without restriction, including without limitation the rights\n        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n        copies of the Software, and to permit persons to whom the Software is\n        furnished to do so, subject to the following conditions:\n        \n        The above copyright notice and this permission notice shall be included in all\n        copies or substantial portions of the Software.\n        \n        THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n        SOFTWARE.\n        ",
    "summary": "Minimal pythonic config library for deep learning experiments",
    "version": "0.3.2",
    "project_urls": {
        "Homepage": "https://github.com/lucasb-eyer/sws",
        "Repository": "https://github.com/lucasb-eyer/sws"
    },
    "split_keywords": [
        "cli",
        " config",
        " configuration",
        " deep learning",
        " experiments"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d7ab4e51bf8803f6afe2dac7467233baf849d76d7c8bc2f008197f5c315032a4",
                "md5": "f1033feaaa0250719119bd74ce5a5231",
                "sha256": "337b8dfafdca0d288fb8c5bbb148fe2af1a8901de972acbf233d8c33290abc6e"
            },
            "downloads": -1,
            "filename": "sws_config-0.3.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f1033feaaa0250719119bd74ce5a5231",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 20733,
            "upload_time": "2025-08-27T19:09:58",
            "upload_time_iso_8601": "2025-08-27T19:09:58.895012Z",
            "url": "https://files.pythonhosted.org/packages/d7/ab/4e51bf8803f6afe2dac7467233baf849d76d7c8bc2f008197f5c315032a4/sws_config-0.3.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d6798d439872f04012aa4ff71255ce96c86a327109c81a35e335660bc1381547",
                "md5": "20a9e7739c14efb75895de43613b9c6f",
                "sha256": "47d546cba75253147683cba77868621406c087b9903ad8ea73642450acd7440f"
            },
            "downloads": -1,
            "filename": "sws_config-0.3.2.tar.gz",
            "has_sig": false,
            "md5_digest": "20a9e7739c14efb75895de43613b9c6f",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 23826,
            "upload_time": "2025-08-27T19:09:59",
            "upload_time_iso_8601": "2025-08-27T19:09:59.929145Z",
            "url": "https://files.pythonhosted.org/packages/d6/79/8d439872f04012aa4ff71255ce96c86a327109c81a35e335660bc1381547/sws_config-0.3.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-27 19:09:59",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "lucasb-eyer",
    "github_project": "sws",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "sws-config"
}
        
Elapsed time: 1.89941s