coleo


Namecoleo JSON
Version 0.3.3 PyPI version JSON
download
home_pagehttps://github.com/breuleux/coleo
SummaryThe nicest way to develop a command-line interface
upload_time2023-09-19 21:13:08
maintainer
docs_urlNone
authorOlivier Breuleux
requires_python>=3.7,<4.0
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            
# Coleo

Coleo is a minimum-effort way to create a command-line interface in Python.

* Declare options where they are used.
* Scale easily to extensive CLIs with dozens of subcommands and options.


## Basic usage

First, define a command line interface as follows:

```python
from coleo import Option, auto_cli, default

@auto_cli
def main():
    # The greeting
    greeting: Option = default("Hello")

    # The name to greet
    name: Option = default("you")

    return f"{greeting}, {name}!"
```

Then you may run it like this on the command line:

```bash
$ python hello.py
Hello, you!
$ python hello.py --name Luke
Hello, Luke!
$ python hello.py --name Luke --greeting "Happy birthday"
Happy birthday, Luke!
$ python hello.py -h
usage: hello.py [-h] [--greeting VALUE] [--name VALUE]

optional arguments:
  -h, --help        show this help message and exit
  --greeting VALUE  The greeting
  --name VALUE      The name to greet
```

* Any variable annotated with `Option` will become an option.
* You can provide a default value with `default(value)`, although you don't have to, if the argument is required.
* If there is a comment above the variable, it will be used as documentation for the option.


## Option types

By default, all arguments are interpreted as strings, but you can easily give a different type to an argument:

```python
@auto_cli
def main():
    # This argument will be converted to an int
    x: Option & int
    # This argument will be converted to a float
    y: Option & float
    return x + y
```

**Boolean flags**

If the type is bool, the option will take no argument, for example:

```python
@auto_cli
def main():
    flag: Option & bool = default(False)
    return "yes!" if flag else "no!"
```

Use it like this:

```bash
$ python script.py --flag
yes!
$ python script.py
no!
```

You can also *negate* the flag, meaning that you want to provide an option that will store False in the variable instead of True. For example:

```python
@auto_cli
def main():
    # [negate]
    flag: Option & bool = default(True)
    return "yes!" if flag else "no!"
```

By default, the above will create a flag called `--no-<optname>`:

```bash
$ python script.py
yes!
$ python script.py --no-flag
no!
```

You can write `[negate: --xyz -n]` if you want the option to be `--xyz` or `-n`. This overrides the default `--no-flag` option.

Note that using `[negate]` will remove `--flag`, because we assume that it is True by default and there is therefore no need for this option.

If you wish, you can have both options that set the flag to True and others that set the flag to False, using `[false-options]`. You can optionally document these options with `[false-options-doc]` (if not provided, Coleo will use a sensible default):

```python
@auto_cli
def main():
    # Set the flag to True
    # [options: -y]
    # [false-options: -n]
    # [false-options-doc: Set the flag to False]
    flag: Option & bool = default(None)
    return flag
```

```bash
$ python script.py
None
$ python script.py -y
True
$ python script.py -n
False
```


**Files**

Use `coleo.FileType` (or `argparse.FileType`, it's the same thing) to open a file to read from or to write to:

```python
@auto_cli
def main():
    grocery_list: Option & coleo.FileType("r")
    with grocery_list as f:
        for food in f.readlines():
            print(f"Gotta buy some {food}")
```

**Config**

You can manipulate configuration files with `coleo.config` or `coleo.ConfigFile`:

```python
@auto_cli
def main():
    # ConfigFile lets you read or write a configuration file
    book: Option & ConfigFile
    contents = book.read()
    contents["xyz"] = "abc"
    book.write(contents)

    # config will read the file for you or parse the argument as JSON
    magazine: Option & config
    print(magazine)
```

Use it simply like this:

```bash
$ python librarian.py --book alice.json --magazine vogue.json
$ python librarian.py --book history.yaml --magazine gamez.toml
$ python librarian.py --book physics.json --magazine '{"a": 1, "b": 2}'
# etc
```

Supported extensions are `json`, `yaml` and `toml` (the latter two require installing the `pyyaml` or `toml` packages).


**Other**

Any function can be used as a "type" for an argument. So for example, if you want to be able to provide lists and dictionaries on the command line you can simply use `json.loads` (although `coleo.config` is usually better, because it can also read files, in various formats):

```python
@auto_cli
def main():
    obj: Option & json.loads
    return type(obj).__name__
```

```bash
$ python json.py --obj 1
int
$ python json.py --obj '"hello"'
str
$ python json.py --obj '{"a": 1, "b": 2}'
dict
```

If you're feeling super feisty and care nothing about safety, you can even use `eval`:

```python
@auto_cli
def main():
    obj: Option & eval
    return type(obj).__name__
```

```bash
$ python eval.py --obj "1 + 2"
int
$ python eval.py --obj "lambda x: x + 1"
function
```


## Customization

Using comments of the form `# [<instruction>: <args ...>]` you can customize the option parser:

```python
@auto_cli
def main():
    # This argument can be given as either --greeting or -g
    # [alias: -g]
    greeting: Option = default("Hello")

    # This argument is positional
    # [positional]
    name: Option = default("you")

    # This argument can only be given as -n
    # [options: -n]
    ntimes: Option & int = default(1)

    for i in range(ntimes):
        print(f"{greeting}, {name}!")
```

The above would be used like this:

```bash
$ python hello.py Alice -g Greetings -n 2
Greetings, Alice!
Greetings, Alice!
```

The following customizations are available:

* `[alias: ...]` defines one or several options that are aliases for the main one. Options are separated by spaces, commas or semicolons.
* `[options: ...]` defines one or several options for this argument, which *override* the default one. Options are separated by spaces, commas or semicolons.
* `[positional]` defines one positional argument.
  * `[positional: n]`: n positional arguments (a list is returned).
  * `[positional: ?]`: one optional positional argument
  * `[positional: *]`: zero or more positional arguments
  * `[positional: +]`: one or more positional arguments
* `[remainder]` represents all arguments that are not matched by the argument parser
* `[nargs: n]` declares that the option takes n arguments
  * `[nargs: ?]`: one optional argument
  * `[nargs: *]`: zero or more arguments
  * `[nargs: +]`: one or more arguments
  * `[nargs: **]` or `[nargs: --]`: all remaining arguments, including --args
* `[action: <action>]` customizes the action to perform
  * `[action: append]` lets you use an option multiple times, accumulating the results in a list (e.g. `python app.py -a 1 -a 2 -a 3`, would put `[1, 2, 3]` in `a`)
* `[metavar: varname]` changes the variable name right after the option in the help string, e.g. `--opt METAVAR`
* `[group: groupname]` puts the option in a named group. Options in the same group will appear together in the help.
* For **bool** options only:
    * `[negate: ...]` changes the option so that it sets the variable to False instead of True when they are given. Space/comma aliases may be provided for the option, otherwise the flag will be named `--no-<optname>`.
    * `[false-options: ]` provide a list of options that set the flag to False.
    * `[false-options-doc: ]` provide a documentation for the options given using the previous statement.


## Subcommands

You can create an interface with a hierarchy of subcommands by decorating a class with `auto_cli`:

```python
@auto_cli
class main:
    class calc:
        def add():
            x: Option & int
            y: Option & int
            return x + y

        def mul():
            x: Option & int
            y: Option & int
            return x * y

        def pow():
            base: Option & int
            exponent: Option & int
            return base ** exponent

    def greet():
        greeting: Option = default("Hello")
        name: Option = default("you")
        return f"{greeting}, {name}!"
```

The class only holds structure and will never be instantiated, so don't add `self` to the argument lists for these functions.

Then you may use it like this:

```bash
$ python multi.py greet --name Alice --greeting Hi
Hi, Alice!
$ python multi.py calc add --x=3 --y=8
11
```


## Sharing arguments

It is possible to share behavior and arguments between subcommands, or to split complex functionality into multiple pieces. For example, maybe multiple subcommands in your application require an API key, which can either be given on the command line or can be read from a file. This is how you would share this behavior across all subcommands:

```python
from coleo import Option, auto_cli, config, default, tooled

@tooled
def apikey():
    # The API key to use
    key: Option = default(None)
    if key is None:
        # If no key parameter is given on the command line, try to read it from
        # some standard location.
        key = config("~/.config/myapp/config.json")["key"]
    return key

@auto_cli
class main:
    def search():
        interface = Application(apikey())
        query: Option
        return interface.search(query)

    def install():
        interface = Application(apikey())
        package: Option
        return interface.install(package)
```

If a function is decorated with `@tooled` and is called from one of the main functions (or from another tooled function), Coleo will search for arguments in that function too. Thus any subcommand that calls `apikey()` will gain a `--key` option.

In addition to this, you can "share" arguments by defining the same argument with the same type in multiple functions. Coleo will set all of them to the same value.

For example, in the example above you could easily let the user specify the path to the file that contains the key, simply by replacing

```python
key = config("~/.config/myapp/config.json")["key"]

# ==>

config_path: Option = default("~/.config/myapp/config.json")
key = config(config_path)["key"]
```

And that `config_path` argument could, of course, be declared in any other function that needs to read some configuration value.


## run_cli

```python
from coleo import Option, auto_cli

@auto_cli
def main():
    x: Option
    return x
```

Is equivalent to:

```python
from coleo import Option, run_cli, tooled

@tooled
def main():
    x: Option
    return x

result = run_cli(main)
if result is not None:
    print(result)
```


## Non-CLI usage

It is possible to set arguments without `auto_cli` using `setvars`:

```python
from coleo import Option, setvars, tooled

@tooled
def greet():
    greeting: Option = default("Hello")
    name: Option = default("you")
    return f"{greeting} {name}!"

with setvars(greeting="Hi", name="Bob"):
    assert greet() == "Hi bob!"
```

Note:

* With `setvars`, you *must* decorate the function with `@tooled` (this is something `auto_cli` does on your behalf).
* `setvars` entirely bypasses the option parsing and the type annotations will not be used to wrap these values. In other words, if a variable is annotated `Option & int` and you provide the value "1", it will remain a string.


### Using with Ptera

Coleo is based on [Ptera](https://github.com/mila-iqia/ptera) and all of Ptera's functionality is de facto available on functions marked as `@tooled`. For example, using the example above:

```python
# Set the variables in the greet function -- it's a bit like making an object
hibob = greet.new(greeting="Hi", name="Bob")
assert hibob() == "Hi Bob!"

# Same as above but this would also change greeting/name in any other function
# that is called by greet, and so on recursively (a bit like dynamic scoping)
hibob = greet.tweaking({"greeting": "Hi", "name": "Bob"})
assert hibob() == "Hi Bob!"

# More complex behavior
from ptera import overlay
with overlay.tweaking({
    "greet(greeting='Bonjour') > name": "Toto"
}):
    assert greet() == "Hello you!"
    assert greet.new(greeting="Hi")() == "Hi you!"
    assert greet.new(greeting="Bonjour")() == "Bonjour toto!"
```

Read the documentation for [Ptera](https://github.com/mila-iqia/ptera) for more information. Note that Ptera is not limited to variables tagged `Option`, it can manipulate *any* variable in a tooled function.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/breuleux/coleo",
    "name": "coleo",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7,<4.0",
    "maintainer_email": "",
    "keywords": "",
    "author": "Olivier Breuleux",
    "author_email": "breuleux@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/75/1a/fcbb9c28a3c2fa89aaed8de7ac44829fd1879615bb5d97dc35ab69ed0440/coleo-0.3.3.tar.gz",
    "platform": null,
    "description": "\n# Coleo\n\nColeo is a minimum-effort way to create a command-line interface in Python.\n\n* Declare options where they are used.\n* Scale easily to extensive CLIs with dozens of subcommands and options.\n\n\n## Basic usage\n\nFirst, define a command line interface as follows:\n\n```python\nfrom coleo import Option, auto_cli, default\n\n@auto_cli\ndef main():\n    # The greeting\n    greeting: Option = default(\"Hello\")\n\n    # The name to greet\n    name: Option = default(\"you\")\n\n    return f\"{greeting}, {name}!\"\n```\n\nThen you may run it like this on the command line:\n\n```bash\n$ python hello.py\nHello, you!\n$ python hello.py --name Luke\nHello, Luke!\n$ python hello.py --name Luke --greeting \"Happy birthday\"\nHappy birthday, Luke!\n$ python hello.py -h\nusage: hello.py [-h] [--greeting VALUE] [--name VALUE]\n\noptional arguments:\n  -h, --help        show this help message and exit\n  --greeting VALUE  The greeting\n  --name VALUE      The name to greet\n```\n\n* Any variable annotated with `Option` will become an option.\n* You can provide a default value with `default(value)`, although you don't have to, if the argument is required.\n* If there is a comment above the variable, it will be used as documentation for the option.\n\n\n## Option types\n\nBy default, all arguments are interpreted as strings, but you can easily give a different type to an argument:\n\n```python\n@auto_cli\ndef main():\n    # This argument will be converted to an int\n    x: Option & int\n    # This argument will be converted to a float\n    y: Option & float\n    return x + y\n```\n\n**Boolean flags**\n\nIf the type is bool, the option will take no argument, for example:\n\n```python\n@auto_cli\ndef main():\n    flag: Option & bool = default(False)\n    return \"yes!\" if flag else \"no!\"\n```\n\nUse it like this:\n\n```bash\n$ python script.py --flag\nyes!\n$ python script.py\nno!\n```\n\nYou can also *negate* the flag, meaning that you want to provide an option that will store False in the variable instead of True. For example:\n\n```python\n@auto_cli\ndef main():\n    # [negate]\n    flag: Option & bool = default(True)\n    return \"yes!\" if flag else \"no!\"\n```\n\nBy default, the above will create a flag called `--no-<optname>`:\n\n```bash\n$ python script.py\nyes!\n$ python script.py --no-flag\nno!\n```\n\nYou can write `[negate: --xyz -n]` if you want the option to be `--xyz` or `-n`. This overrides the default `--no-flag` option.\n\nNote that using `[negate]` will remove `--flag`, because we assume that it is True by default and there is therefore no need for this option.\n\nIf you wish, you can have both options that set the flag to True and others that set the flag to False, using `[false-options]`. You can optionally document these options with `[false-options-doc]` (if not provided, Coleo will use a sensible default):\n\n```python\n@auto_cli\ndef main():\n    # Set the flag to True\n    # [options: -y]\n    # [false-options: -n]\n    # [false-options-doc: Set the flag to False]\n    flag: Option & bool = default(None)\n    return flag\n```\n\n```bash\n$ python script.py\nNone\n$ python script.py -y\nTrue\n$ python script.py -n\nFalse\n```\n\n\n**Files**\n\nUse `coleo.FileType` (or `argparse.FileType`, it's the same thing) to open a file to read from or to write to:\n\n```python\n@auto_cli\ndef main():\n    grocery_list: Option & coleo.FileType(\"r\")\n    with grocery_list as f:\n        for food in f.readlines():\n            print(f\"Gotta buy some {food}\")\n```\n\n**Config**\n\nYou can manipulate configuration files with `coleo.config` or `coleo.ConfigFile`:\n\n```python\n@auto_cli\ndef main():\n    # ConfigFile lets you read or write a configuration file\n    book: Option & ConfigFile\n    contents = book.read()\n    contents[\"xyz\"] = \"abc\"\n    book.write(contents)\n\n    # config will read the file for you or parse the argument as JSON\n    magazine: Option & config\n    print(magazine)\n```\n\nUse it simply like this:\n\n```bash\n$ python librarian.py --book alice.json --magazine vogue.json\n$ python librarian.py --book history.yaml --magazine gamez.toml\n$ python librarian.py --book physics.json --magazine '{\"a\": 1, \"b\": 2}'\n# etc\n```\n\nSupported extensions are `json`, `yaml` and `toml` (the latter two require installing the `pyyaml` or `toml` packages).\n\n\n**Other**\n\nAny function can be used as a \"type\" for an argument. So for example, if you want to be able to provide lists and dictionaries on the command line you can simply use `json.loads` (although `coleo.config` is usually better, because it can also read files, in various formats):\n\n```python\n@auto_cli\ndef main():\n    obj: Option & json.loads\n    return type(obj).__name__\n```\n\n```bash\n$ python json.py --obj 1\nint\n$ python json.py --obj '\"hello\"'\nstr\n$ python json.py --obj '{\"a\": 1, \"b\": 2}'\ndict\n```\n\nIf you're feeling super feisty and care nothing about safety, you can even use `eval`:\n\n```python\n@auto_cli\ndef main():\n    obj: Option & eval\n    return type(obj).__name__\n```\n\n```bash\n$ python eval.py --obj \"1 + 2\"\nint\n$ python eval.py --obj \"lambda x: x + 1\"\nfunction\n```\n\n\n## Customization\n\nUsing comments of the form `# [<instruction>: <args ...>]` you can customize the option parser:\n\n```python\n@auto_cli\ndef main():\n    # This argument can be given as either --greeting or -g\n    # [alias: -g]\n    greeting: Option = default(\"Hello\")\n\n    # This argument is positional\n    # [positional]\n    name: Option = default(\"you\")\n\n    # This argument can only be given as -n\n    # [options: -n]\n    ntimes: Option & int = default(1)\n\n    for i in range(ntimes):\n        print(f\"{greeting}, {name}!\")\n```\n\nThe above would be used like this:\n\n```bash\n$ python hello.py Alice -g Greetings -n 2\nGreetings, Alice!\nGreetings, Alice!\n```\n\nThe following customizations are available:\n\n* `[alias: ...]` defines one or several options that are aliases for the main one. Options are separated by spaces, commas or semicolons.\n* `[options: ...]` defines one or several options for this argument, which *override* the default one. Options are separated by spaces, commas or semicolons.\n* `[positional]` defines one positional argument.\n  * `[positional: n]`: n positional arguments (a list is returned).\n  * `[positional: ?]`: one optional positional argument\n  * `[positional: *]`: zero or more positional arguments\n  * `[positional: +]`: one or more positional arguments\n* `[remainder]` represents all arguments that are not matched by the argument parser\n* `[nargs: n]` declares that the option takes n arguments\n  * `[nargs: ?]`: one optional argument\n  * `[nargs: *]`: zero or more arguments\n  * `[nargs: +]`: one or more arguments\n  * `[nargs: **]` or `[nargs: --]`: all remaining arguments, including --args\n* `[action: <action>]` customizes the action to perform\n  * `[action: append]` lets you use an option multiple times, accumulating the results in a list (e.g. `python app.py -a 1 -a 2 -a 3`, would put `[1, 2, 3]` in `a`)\n* `[metavar: varname]` changes the variable name right after the option in the help string, e.g. `--opt METAVAR`\n* `[group: groupname]` puts the option in a named group. Options in the same group will appear together in the help.\n* For **bool** options only:\n    * `[negate: ...]` changes the option so that it sets the variable to False instead of True when they are given. Space/comma aliases may be provided for the option, otherwise the flag will be named `--no-<optname>`.\n    * `[false-options: ]` provide a list of options that set the flag to False.\n    * `[false-options-doc: ]` provide a documentation for the options given using the previous statement.\n\n\n## Subcommands\n\nYou can create an interface with a hierarchy of subcommands by decorating a class with `auto_cli`:\n\n```python\n@auto_cli\nclass main:\n    class calc:\n        def add():\n            x: Option & int\n            y: Option & int\n            return x + y\n\n        def mul():\n            x: Option & int\n            y: Option & int\n            return x * y\n\n        def pow():\n            base: Option & int\n            exponent: Option & int\n            return base ** exponent\n\n    def greet():\n        greeting: Option = default(\"Hello\")\n        name: Option = default(\"you\")\n        return f\"{greeting}, {name}!\"\n```\n\nThe class only holds structure and will never be instantiated, so don't add `self` to the argument lists for these functions.\n\nThen you may use it like this:\n\n```bash\n$ python multi.py greet --name Alice --greeting Hi\nHi, Alice!\n$ python multi.py calc add --x=3 --y=8\n11\n```\n\n\n## Sharing arguments\n\nIt is possible to share behavior and arguments between subcommands, or to split complex functionality into multiple pieces. For example, maybe multiple subcommands in your application require an API key, which can either be given on the command line or can be read from a file. This is how you would share this behavior across all subcommands:\n\n```python\nfrom coleo import Option, auto_cli, config, default, tooled\n\n@tooled\ndef apikey():\n    # The API key to use\n    key: Option = default(None)\n    if key is None:\n        # If no key parameter is given on the command line, try to read it from\n        # some standard location.\n        key = config(\"~/.config/myapp/config.json\")[\"key\"]\n    return key\n\n@auto_cli\nclass main:\n    def search():\n        interface = Application(apikey())\n        query: Option\n        return interface.search(query)\n\n    def install():\n        interface = Application(apikey())\n        package: Option\n        return interface.install(package)\n```\n\nIf a function is decorated with `@tooled` and is called from one of the main functions (or from another tooled function), Coleo will search for arguments in that function too. Thus any subcommand that calls `apikey()` will gain a `--key` option.\n\nIn addition to this, you can \"share\" arguments by defining the same argument with the same type in multiple functions. Coleo will set all of them to the same value.\n\nFor example, in the example above you could easily let the user specify the path to the file that contains the key, simply by replacing\n\n```python\nkey = config(\"~/.config/myapp/config.json\")[\"key\"]\n\n# ==>\n\nconfig_path: Option = default(\"~/.config/myapp/config.json\")\nkey = config(config_path)[\"key\"]\n```\n\nAnd that `config_path` argument could, of course, be declared in any other function that needs to read some configuration value.\n\n\n## run_cli\n\n```python\nfrom coleo import Option, auto_cli\n\n@auto_cli\ndef main():\n    x: Option\n    return x\n```\n\nIs equivalent to:\n\n```python\nfrom coleo import Option, run_cli, tooled\n\n@tooled\ndef main():\n    x: Option\n    return x\n\nresult = run_cli(main)\nif result is not None:\n    print(result)\n```\n\n\n## Non-CLI usage\n\nIt is possible to set arguments without `auto_cli` using `setvars`:\n\n```python\nfrom coleo import Option, setvars, tooled\n\n@tooled\ndef greet():\n    greeting: Option = default(\"Hello\")\n    name: Option = default(\"you\")\n    return f\"{greeting} {name}!\"\n\nwith setvars(greeting=\"Hi\", name=\"Bob\"):\n    assert greet() == \"Hi bob!\"\n```\n\nNote:\n\n* With `setvars`, you *must* decorate the function with `@tooled` (this is something `auto_cli` does on your behalf).\n* `setvars` entirely bypasses the option parsing and the type annotations will not be used to wrap these values. In other words, if a variable is annotated `Option & int` and you provide the value \"1\", it will remain a string.\n\n\n### Using with Ptera\n\nColeo is based on [Ptera](https://github.com/mila-iqia/ptera) and all of Ptera's functionality is de facto available on functions marked as `@tooled`. For example, using the example above:\n\n```python\n# Set the variables in the greet function -- it's a bit like making an object\nhibob = greet.new(greeting=\"Hi\", name=\"Bob\")\nassert hibob() == \"Hi Bob!\"\n\n# Same as above but this would also change greeting/name in any other function\n# that is called by greet, and so on recursively (a bit like dynamic scoping)\nhibob = greet.tweaking({\"greeting\": \"Hi\", \"name\": \"Bob\"})\nassert hibob() == \"Hi Bob!\"\n\n# More complex behavior\nfrom ptera import overlay\nwith overlay.tweaking({\n    \"greet(greeting='Bonjour') > name\": \"Toto\"\n}):\n    assert greet() == \"Hello you!\"\n    assert greet.new(greeting=\"Hi\")() == \"Hi you!\"\n    assert greet.new(greeting=\"Bonjour\")() == \"Bonjour toto!\"\n```\n\nRead the documentation for [Ptera](https://github.com/mila-iqia/ptera) for more information. Note that Ptera is not limited to variables tagged `Option`, it can manipulate *any* variable in a tooled function.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "The nicest way to develop a command-line interface",
    "version": "0.3.3",
    "project_urls": {
        "Homepage": "https://github.com/breuleux/coleo",
        "Repository": "https://github.com/breuleux/coleo"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "cccf36ecf45a0c8e63999e953864f575d51b916136e2080ebab15065ee4baf6a",
                "md5": "9e1c6ae0b6b55577b06a5434c0f3ad76",
                "sha256": "002da5966836c59e1d69a19f93759920043db40f612c8ffe3f87231379bd1b03"
            },
            "downloads": -1,
            "filename": "coleo-0.3.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "9e1c6ae0b6b55577b06a5434c0f3ad76",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7,<4.0",
            "size": 12274,
            "upload_time": "2023-09-19T21:13:07",
            "upload_time_iso_8601": "2023-09-19T21:13:07.362426Z",
            "url": "https://files.pythonhosted.org/packages/cc/cf/36ecf45a0c8e63999e953864f575d51b916136e2080ebab15065ee4baf6a/coleo-0.3.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "751afcbb9c28a3c2fa89aaed8de7ac44829fd1879615bb5d97dc35ab69ed0440",
                "md5": "c02ee91a1b06eb34be1dc0eab865f122",
                "sha256": "33d93991ad205cf1eebfbb5c1df34c0aa8eceda89b59d0970c4850dbbe53914a"
            },
            "downloads": -1,
            "filename": "coleo-0.3.3.tar.gz",
            "has_sig": false,
            "md5_digest": "c02ee91a1b06eb34be1dc0eab865f122",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7,<4.0",
            "size": 16868,
            "upload_time": "2023-09-19T21:13:08",
            "upload_time_iso_8601": "2023-09-19T21:13:08.713306Z",
            "url": "https://files.pythonhosted.org/packages/75/1a/fcbb9c28a3c2fa89aaed8de7ac44829fd1879615bb5d97dc35ab69ed0440/coleo-0.3.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-09-19 21:13:08",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "breuleux",
    "github_project": "coleo",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "coleo"
}
        
Elapsed time: 0.12087s