# cmdi - Command Interface
## Description
A decorator `@command` that applies a special interface called the _Command Interface_ to its decorated function. Initially written for the _buildlib_.
The _Command Interface_ allows you to control the execution of a function via the _Command Interface_:
- It allows you to save/redirect/mute output streams (stdout/stderr) for its decorated function. This works on file descriptor level. It's possible to redirect output of subprocesses and C code as well.
- It allows you to catch exceptions for its decorated function and return them with the `CmdResult()`, including _return codes_, _error messages_ and colored _status messages_.
- It allows you to print status messages and summaries for a command at runtime.
- And more...
A function that is decorated with `@command` can receive a set of special keyword arguments (`_verbose=...`, `_stdout=...`, `_stderr=...`, `catch_err=...`, etc.) and it returns a `CmdResult()` object.
## Requirements
Python `>= 3.9`
## Install
```
pip install cmdi
```
## Usage
### The `@command` decorator
Use the `@command` decorator to apply the _command interface_ to a function.
```python
from cmdi import command
@command
def foo_cmd(x, **cmdargs) -> int:
print(x)
return x * 2
```
Now you can use `foo_cmd` as a `command`:
```python
result = foo_cmd(10)
```
Which will print the following output (in color):
```
Cmd: foo_cmd
------------
10
foo_cmd: Ok
```
and return a `CmdResult` object:
```python
CmdResult(
val=20,
code=0,
name='foo_cmd',
status=Status.ok,
color=StatusColor.green,
stdout=None,
stderr=None
)
```
### Command Function Arguments
You can define the behavior of a command function using a set of special keyword arguments that are applied to the decorated function.
In this example we redirect the output of `foo_cmd` to an in-memory file writer and catch exceptions. The output and information of the exception are then returned with the `CmdResult()` object:
```python
from cmdi import CmdResult, Pipe
result = foo_cmd(10, _stdout=Pipe(), _catch_err=True)
isinstance(result, CmdResult) # True
print(result.stdout) # prints caught output.
```
More about special keyword arguments can be found in the API documentation below.
### Customizing the Result of a command function
A command always returns a `CmdResult` object, for which the `@command` wrapper function automatically sets the values for the fields (`code`, `status`, `color`, ...), which is good enough in many situations. But sometimes you need fine grained control over the return values, e.g. to create function specific return codes. In these situations you can explicitly return a `CmdResult` object with some or all of the fields set by yourself:
```python
@command
def foo_cmd(x: str, **cmdargs) -> CmdResult:
print(x)
somestr = "foo" + x
if x == "bar":
code = 0
else:
code = 42
# Return a customized Command Result:
return CmdResult(
val=somestr,
code=code,
)
```
**Note:** In the example above, we return a customized `CmdResult` for which we only customize the fields `val` and `code`. You can customize every field of the `CmdResult` object (optionally). The fields you leave out are set automatically.
### Command Interface Function Wrappers
Sometimes you want to use the _Command Interface_ for an existing function, without touching the function definition. You can do so by creating a _Command Interface Function Wrapper_:
```python
from cmdi import command, strip_cmdargs, CmdResult
# This function wraps the Command Interface around an existing function:
@command
def foo_cmd(x, **cmdargs) -> CmdResult:
return foo(**strip_cmdargs(loclas()))
# The original function that is being wrapped:
def foo(x) -> int:
print(x)
return x * 2
```
### Command Interface Function Wrappers and `subprocess` return codes.
If you need to create a _Command Interface Function Wrapper_ for an existing function that runs a `subprocess` and your command depends on the `returncode` of that, you can use the `subprocess.CalledProcessError` exception to compose something. E.g.:
```python
import subprocess as sp
from cmdi import command, CmdResult, Status
@command
def foo_cmd(x, **cmdargs) -> CmdResult:
try:
return foo(**strip_cmdargs(locals()))
except sp.CalledProcessError as e:
if e.returncode == 13:
return CmdResult(
code=e.returncode,
status=Status.ok,
)
elif e.returncode == 42:
return CmdResult(
code=e.returncode,
status=Status.skip,
)
else:
raise sp.CalledProcessError(e.returncode, e.args)
def foo(x) -> int:
return sp.run(["my_arg"], check=True, ...)
```
## API
### decorator `@command`
This decorator allows you to apply the _command interface_ to a function.
A function decorated with `@command` can take the following keyword arguments:
#### `_verbose: bool = True`
Enable/Disable printing of command header and status information during runtime.
**Example:**
```python
result = my_command_func("some_arg", _verbose=False)
```
#### `_color: bool = True`
Enable/Disable color for command header and status information.
**Example:**
```python
result = my_command_func("some_arg", _color=False)
```
#### `_stdout: Optional[Pipe] = None`
Redirect stdout of the child function. More on `Pipe()` below.
**Example:**
```python
from cmdi import Pipe
pipe = Pipe(text=False, tty=True, ...) # See Pipe doc for arguments...
result = my_command_func('foo', _stdout=pipe)
print(result.stdout) # Prints the caught ouput.
```
#### `_stderr: Union[Optional[Pipe], STDOUT] = None`
Redirect stderr of the child function. More on `Pipe()` below.
**Example:**
```python
from cmdi import Pipe
pipe = Pipe(text=False, tty=True, ...) # See Pipe doc for arguments...
result = my_command_func('foo', _stderr=pipe))
print(result.stderr) # Prints the caught ouput.
```
If you want to redirect `stderr` to `stdout`, you can use this:
```python
from cmdi import STDOUT
result = my_command_func('foo', _stderr=STDOUT))
```
#### `_catch_err: bool = True`
Catch errors from child function.
This will let the runtime continue, even if a child function throws an exception. If an exception occurs the `CmdResult` object will provide information about the error at `result.stderr`, `result.code` and `result.status`. The status message will appear in red.
**Example:**
```python
from cmdi import Pipe
r = my_command_func("some_arg", _catch_err=True, _stderr=Pipe())
r.status # Error
r.code # 1
r.stdout # The stderr output from the function call.
```
### class `CmdResult()`
The command result object.
A function decorated with `@command` returns a `CmdResult` object:
```python
class CmdResult:
val: T, # The return value of the wrapped function.
code: int, # Return code
name: str, # Command name. By default the name of the wrapped function.
status: Optional[Status],
color: Optional[StatusColor],
stdout: Optional[Union[str, bytes]] = None,
stderr: Optional[Union[str, bytes]] = None,
```
### dataclass `Pipe()`
Use this type to configure `stdout`/`stderr` for a command call.
**Parameters**
- `save: bool = True` - Save the function output if `True`.
- `text: bool = True` - Save function output as text if `True` else save as bytes.
- `dup: bool = False` - Redirect output at file descriptor level if `True`. This allows you to redirect output of subprocesses and C code. It's *dup* because we're duping file descriptors.
- `tty: bool = False` - Keep ANSI sequences for saved output if `True`, else strip ANSI sequences.
- `mute: bool = False` - Mute output of function call in terminal if `True`. NOTE: You can still save and return the output if this is enabled.
**Example:**
```python
from cmdi import CmdResult, Pipe
out_pipe = Pipe(text=False, dup=True, mute=True)
err_pipe = Pipe(text=False, dup=True, mute=False)
result = foo_cmd(10, _stdout=out_pipe, _stderr=err_pipe, _catch_err=True)
print(result.stdout) # prints caught output.
print(result.stderr) # prints caught output.
```
### Redirect output of functions that run subprocesses or external/foreign/C code.
If you want to redirect the output of a function that runs a subprocess or calls foreign code, you have to use a `Pipe` with the argument `dup=True`. This will catch the output of stdout/stderr at a lower level (by duping file descriptors):
```python
import subprocess
from cmdi import command, Pipe
@command
def foo(x, **cmdargs) -> CmdResult:
subprocess.run("my_script")
# Catch stdout of the function via low level redirect:
foo(_stdout=Pipe(dup=True))
```
### function `strip_cmdargs(locals_)`
**Parameters**
- `locals_: Dict[str, Any]`
**Returns**
- `Dict[str, Any]`
Remove cmdargs from dictionary.
This function helps us to easily create _Command Interface Function Wrappers_.
Example usage:
```python
def foo(x):
# Do a lot of stuff
return x * 2
@command
def foo_cmd(x, **cmdargs):
return foo(strip_cmdargs(locals()))
```
### function `print_title(result, color, file)`
**Parameter**
- `result: CmdResult`
- `color: bool = True`
- `file: Optional[IO[str]] = None`
**Returns**
- `None`
Print the title for a command result.
**Example usage:**
```python
result = my_cmd('foo')
print_title(result)
```
Output:
```
Cmd: my_cmd
-----------
```
### function `print_status(result, color, file)`
**Parameter**
- `result: CmdResult`
- `color: bool = True`
- `file: Optional[IO[str]] = None`
**Returns**
- `None`
Print the status of a command result.
**Example usage:**
```python
result = my_cmd('foo')
print_status(result)
```
Output:
```
my_cmd: Ok
```
### function `print_result(result, color, file)`
**Parameter**
- `result: CmdResult`
- `color: bool = True`
- `file: Optional[IO[str]] = None`
**Returns**
- `None`
Print out the CmdResult object.
**Example usage:**
```python
result = my_cmd('foo')
print_result(result)
```
Output:
```
Cmd: my_cmd
-----------
Stdout:
Runtime output of my_cmd...
Stderr:
Some err
foo_cmd3: Ok
```
### function `print_summary(results, color, headline, file)`
**Parameter**
- `results: CmdResult`
- `color: bool = True`
- `headline: bool = True`
- `file: Optional[IO[str]] = None`
**Returns**
- `None`
Print out a summary of one or more command results.
**Example usage:**
```python
from cmdi import print_summary
results = []
results.append(my_foo_cmd())
results.append(my_bar_cmd())
results.append(my_baz_cmd())
print_summary(results)
```
Output:
```
Cmd: my_foo_cmd
---------------
stdout of foo function...
my_foo_cmd: Ok
Cmd: my_bar_cmd
---------------
stdout of bar function...
my_bar_cmd: Ok
Cmd: my_baz_cmd
---------------
stdout of baz function...
my_baz_cmd: Ok
```
### function `read_popen_pipes(p, interval)`
**Parameter**
- `p: subprocess.Popen`
- `interval: int = 10` - The interval which the output streams are read and written with.
**Returns**
- `Iterator[Tuple[str, str]]`
This creates an iterator which returns Popen pipes line by line for both `stdout` and `stderr` separately in realtime.
**Example usage:**
```python
from cmdi import POPEN_DEFAULTS, read_popen_pipes
p = subprocess.Popen(mycmd, **POPEN_DEFAULTS)
for out_line, err_line in read_popen_pipes:
print(out_line, end='')
print(err_line, end='')
code = p.poll()
```
## Run Tests for this library
NOTE: Some test results must be read visually by the human user.
```
poetry run pytest --capture=no tests
```
Raw data
{
"_id": null,
"home_page": "https://github.com/feluxe/cmdi",
"name": "cmdi",
"maintainer": "feluxe",
"docs_url": null,
"requires_python": ">=3.9,<4.0",
"maintainer_email": "felix@meyerwolters.de",
"keywords": "command,interface,wrapper,handler,capture,stdout,stderr",
"author": "feluxe",
"author_email": "felix@meyerwolters.de",
"download_url": "https://files.pythonhosted.org/packages/73/70/67e82fa9e1e1a863ad9efd6e41717c31b10ee3aaaa3965491a439cd4be65/cmdi-2.0.0.tar.gz",
"platform": null,
"description": "# cmdi - Command Interface\n\n\n## Description\n\nA decorator `@command` that applies a special interface called the _Command Interface_ to its decorated function. Initially written for the _buildlib_.\n\nThe _Command Interface_ allows you to control the execution of a function via the _Command Interface_:\n\n- It allows you to save/redirect/mute output streams (stdout/stderr) for its decorated function. This works on file descriptor level. It's possible to redirect output of subprocesses and C code as well.\n- It allows you to catch exceptions for its decorated function and return them with the `CmdResult()`, including _return codes_, _error messages_ and colored _status messages_.\n- It allows you to print status messages and summaries for a command at runtime.\n- And more...\n\nA function that is decorated with `@command` can receive a set of special keyword arguments (`_verbose=...`, `_stdout=...`, `_stderr=...`, `catch_err=...`, etc.) and it returns a `CmdResult()` object.\n\n\n## Requirements\n\nPython `>= 3.9`\n\n\n## Install\n\n```\npip install cmdi\n```\n\n\n## Usage\n\n\n### The `@command` decorator\n\nUse the `@command` decorator to apply the _command interface_ to a function.\n\n```python\nfrom cmdi import command\n\n@command\ndef foo_cmd(x, **cmdargs) -> int:\n print(x)\n return x * 2\n```\n\nNow you can use `foo_cmd` as a `command`:\n\n```python\nresult = foo_cmd(10)\n```\n\nWhich will print the following output (in color):\n\n```\nCmd: foo_cmd\n------------\n10\nfoo_cmd: Ok\n```\n\nand return a `CmdResult` object:\n\n```python\nCmdResult(\n val=20,\n code=0,\n name='foo_cmd',\n status=Status.ok,\n color=StatusColor.green,\n stdout=None,\n stderr=None\n)\n```\n\n\n### Command Function Arguments\n\nYou can define the behavior of a command function using a set of special keyword arguments that are applied to the decorated function.\n\nIn this example we redirect the output of `foo_cmd` to an in-memory file writer and catch exceptions. The output and information of the exception are then returned with the `CmdResult()` object:\n\n```python\nfrom cmdi import CmdResult, Pipe\n\n\nresult = foo_cmd(10, _stdout=Pipe(), _catch_err=True)\n\nisinstance(result, CmdResult) # True\n\nprint(result.stdout) # prints caught output.\n```\n\nMore about special keyword arguments can be found in the API documentation below.\n\n\n### Customizing the Result of a command function\n\nA command always returns a `CmdResult` object, for which the `@command` wrapper function automatically sets the values for the fields (`code`, `status`, `color`, ...), which is good enough in many situations. But sometimes you need fine grained control over the return values, e.g. to create function specific return codes. In these situations you can explicitly return a `CmdResult` object with some or all of the fields set by yourself:\n\n```python\n@command\ndef foo_cmd(x: str, **cmdargs) -> CmdResult:\n\n print(x)\n somestr = \"foo\" + x\n\n if x == \"bar\":\n code = 0\n else:\n code = 42\n\n # Return a customized Command Result:\n\n return CmdResult(\n val=somestr,\n code=code,\n )\n```\n\n**Note:** In the example above, we return a customized `CmdResult` for which we only customize the fields `val` and `code`. You can customize every field of the `CmdResult` object (optionally). The fields you leave out are set automatically.\n\n\n### Command Interface Function Wrappers\n\nSometimes you want to use the _Command Interface_ for an existing function, without touching the function definition. You can do so by creating a _Command Interface Function Wrapper_:\n\n```python\nfrom cmdi import command, strip_cmdargs, CmdResult\n\n# This function wraps the Command Interface around an existing function:\n\n@command\ndef foo_cmd(x, **cmdargs) -> CmdResult:\n return foo(**strip_cmdargs(loclas()))\n\n\n# The original function that is being wrapped:\n\ndef foo(x) -> int:\n print(x)\n return x * 2\n```\n\n\n### Command Interface Function Wrappers and `subprocess` return codes.\n\nIf you need to create a _Command Interface Function Wrapper_ for an existing function that runs a `subprocess` and your command depends on the `returncode` of that, you can use the `subprocess.CalledProcessError` exception to compose something. E.g.:\n\n```python\nimport subprocess as sp\nfrom cmdi import command, CmdResult, Status\n\n@command\ndef foo_cmd(x, **cmdargs) -> CmdResult:\n\n try:\n return foo(**strip_cmdargs(locals()))\n\n except sp.CalledProcessError as e:\n\n if e.returncode == 13:\n return CmdResult(\n code=e.returncode,\n status=Status.ok,\n )\n elif e.returncode == 42:\n return CmdResult(\n code=e.returncode,\n status=Status.skip,\n )\n else:\n raise sp.CalledProcessError(e.returncode, e.args)\n\n\ndef foo(x) -> int:\n return sp.run([\"my_arg\"], check=True, ...)\n\n```\n\n\n## API\n\n### decorator `@command`\n\nThis decorator allows you to apply the _command interface_ to a function.\n\nA function decorated with `@command` can take the following keyword arguments:\n\n\n#### `_verbose: bool = True`\n\nEnable/Disable printing of command header and status information during runtime.\n\n**Example:**\n\n```python\nresult = my_command_func(\"some_arg\", _verbose=False)\n```\n\n\n#### `_color: bool = True`\n\nEnable/Disable color for command header and status information.\n\n**Example:**\n\n```python\nresult = my_command_func(\"some_arg\", _color=False)\n```\n\n\n#### `_stdout: Optional[Pipe] = None`\n\nRedirect stdout of the child function. More on `Pipe()` below.\n\n**Example:**\n\n```python\nfrom cmdi import Pipe\n\npipe = Pipe(text=False, tty=True, ...) # See Pipe doc for arguments...\n\nresult = my_command_func('foo', _stdout=pipe)\n\nprint(result.stdout) # Prints the caught ouput.\n```\n\n\n#### `_stderr: Union[Optional[Pipe], STDOUT] = None`\n\nRedirect stderr of the child function. More on `Pipe()` below.\n\n**Example:**\n\n```python\nfrom cmdi import Pipe\n\npipe = Pipe(text=False, tty=True, ...) # See Pipe doc for arguments...\n\nresult = my_command_func('foo', _stderr=pipe))\n\nprint(result.stderr) # Prints the caught ouput.\n```\n\nIf you want to redirect `stderr` to `stdout`, you can use this:\n\n```python\nfrom cmdi import STDOUT\n\nresult = my_command_func('foo', _stderr=STDOUT))\n```\n\n\n#### `_catch_err: bool = True`\n\nCatch errors from child function.\n\nThis will let the runtime continue, even if a child function throws an exception. If an exception occurs the `CmdResult` object will provide information about the error at `result.stderr`, `result.code` and `result.status`. The status message will appear in red.\n\n**Example:**\n\n```python\nfrom cmdi import Pipe\n\nr = my_command_func(\"some_arg\", _catch_err=True, _stderr=Pipe())\n\nr.status # Error\nr.code # 1\nr.stdout # The stderr output from the function call.\n\n```\n\n\n### class `CmdResult()`\n\nThe command result object.\n\nA function decorated with `@command` returns a `CmdResult` object:\n\n```python\nclass CmdResult:\n val: T, # The return value of the wrapped function.\n code: int, # Return code\n name: str, # Command name. By default the name of the wrapped function.\n status: Optional[Status],\n color: Optional[StatusColor],\n stdout: Optional[Union[str, bytes]] = None,\n stderr: Optional[Union[str, bytes]] = None,\n```\n\n\n### dataclass `Pipe()`\n\nUse this type to configure `stdout`/`stderr` for a command call.\n\n**Parameters**\n\n- `save: bool = True` - Save the function output if `True`.\n- `text: bool = True` - Save function output as text if `True` else save as bytes.\n- `dup: bool = False` - Redirect output at file descriptor level if `True`. This allows you to redirect output of subprocesses and C code. It's *dup* because we're duping file descriptors.\n- `tty: bool = False` - Keep ANSI sequences for saved output if `True`, else strip ANSI sequences.\n- `mute: bool = False` - Mute output of function call in terminal if `True`. NOTE: You can still save and return the output if this is enabled.\n\n**Example:**\n\n```python\nfrom cmdi import CmdResult, Pipe\n\nout_pipe = Pipe(text=False, dup=True, mute=True)\nerr_pipe = Pipe(text=False, dup=True, mute=False)\n\nresult = foo_cmd(10, _stdout=out_pipe, _stderr=err_pipe, _catch_err=True)\n\nprint(result.stdout) # prints caught output.\nprint(result.stderr) # prints caught output.\n```\n\n\n### Redirect output of functions that run subprocesses or external/foreign/C code.\n\nIf you want to redirect the output of a function that runs a subprocess or calls foreign code, you have to use a `Pipe` with the argument `dup=True`. This will catch the output of stdout/stderr at a lower level (by duping file descriptors):\n\n```python\nimport subprocess\nfrom cmdi import command, Pipe\n\n@command\ndef foo(x, **cmdargs) -> CmdResult:\n subprocess.run(\"my_script\")\n\n# Catch stdout of the function via low level redirect:\nfoo(_stdout=Pipe(dup=True))\n```\n\n\n### function `strip_cmdargs(locals_)`\n\n**Parameters**\n\n- `locals_: Dict[str, Any]`\n\n**Returns**\n\n- `Dict[str, Any]`\n\nRemove cmdargs from dictionary.\nThis function helps us to easily create _Command Interface Function Wrappers_.\n\nExample usage:\n\n```python\ndef foo(x):\n # Do a lot of stuff\n return x * 2\n\n@command\ndef foo_cmd(x, **cmdargs):\n return foo(strip_cmdargs(locals()))\n```\n\n\n### function `print_title(result, color, file)`\n\n**Parameter**\n\n- `result: CmdResult`\n- `color: bool = True`\n- `file: Optional[IO[str]] = None`\n\n**Returns**\n\n- `None`\n\nPrint the title for a command result.\n\n**Example usage:**\n\n```python\nresult = my_cmd('foo')\n\nprint_title(result)\n```\n\nOutput:\n\n```\nCmd: my_cmd\n-----------\n```\n\n\n### function `print_status(result, color, file)`\n\n**Parameter**\n\n- `result: CmdResult`\n- `color: bool = True`\n- `file: Optional[IO[str]] = None`\n\n**Returns**\n\n- `None`\n\nPrint the status of a command result.\n\n**Example usage:**\n\n```python\nresult = my_cmd('foo')\n\nprint_status(result)\n```\n\nOutput:\n\n```\nmy_cmd: Ok\n```\n\n\n### function `print_result(result, color, file)`\n\n**Parameter**\n\n- `result: CmdResult`\n- `color: bool = True`\n- `file: Optional[IO[str]] = None`\n\n**Returns**\n\n- `None`\n\nPrint out the CmdResult object.\n\n**Example usage:**\n\n```python\nresult = my_cmd('foo')\n\nprint_result(result)\n```\n\nOutput:\n\n```\nCmd: my_cmd\n-----------\nStdout:\nRuntime output of my_cmd...\nStderr:\nSome err\nfoo_cmd3: Ok\n```\n\n\n### function `print_summary(results, color, headline, file)`\n\n**Parameter**\n\n- `results: CmdResult`\n- `color: bool = True`\n- `headline: bool = True`\n- `file: Optional[IO[str]] = None`\n\n**Returns**\n\n- `None`\n\nPrint out a summary of one or more command results.\n\n**Example usage:**\n\n```python\nfrom cmdi import print_summary\n\nresults = []\n\nresults.append(my_foo_cmd())\nresults.append(my_bar_cmd())\nresults.append(my_baz_cmd())\n\nprint_summary(results)\n```\n\nOutput:\n\n```\nCmd: my_foo_cmd\n---------------\nstdout of foo function...\nmy_foo_cmd: Ok\n\nCmd: my_bar_cmd\n---------------\nstdout of bar function...\nmy_bar_cmd: Ok\n\nCmd: my_baz_cmd\n---------------\nstdout of baz function...\nmy_baz_cmd: Ok\n```\n\n\n### function `read_popen_pipes(p, interval)`\n\n**Parameter**\n\n- `p: subprocess.Popen`\n- `interval: int = 10` - The interval which the output streams are read and written with.\n\n**Returns**\n\n- `Iterator[Tuple[str, str]]`\n\nThis creates an iterator which returns Popen pipes line by line for both `stdout` and `stderr` separately in realtime.\n\n**Example usage:**\n\n```python\nfrom cmdi import POPEN_DEFAULTS, read_popen_pipes\n\np = subprocess.Popen(mycmd, **POPEN_DEFAULTS)\n\nfor out_line, err_line in read_popen_pipes:\n print(out_line, end='')\n print(err_line, end='')\n\ncode = p.poll()\n```\n\n\n## Run Tests for this library\n\nNOTE: Some test results must be read visually by the human user.\n\n```\npoetry run pytest --capture=no tests\n```\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "A command interface",
"version": "2.0.0",
"project_urls": {
"Homepage": "https://github.com/feluxe/cmdi",
"Repository": "https://github.com/feluxe/cmdi"
},
"split_keywords": [
"command",
"interface",
"wrapper",
"handler",
"capture",
"stdout",
"stderr"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "beaed44c7d598b66beaea527e6d8d60fd5046d4527e8c59f07de45c9b5577a75",
"md5": "36e95a828d68115e34fd197465b474c4",
"sha256": "ecd1747c0825a28674c2ba792cffd4aa6f27ecad1ea34c60cb10fcba2bf4f14e"
},
"downloads": -1,
"filename": "cmdi-2.0.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "36e95a828d68115e34fd197465b474c4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9,<4.0",
"size": 15503,
"upload_time": "2023-11-30T00:41:31",
"upload_time_iso_8601": "2023-11-30T00:41:31.890435Z",
"url": "https://files.pythonhosted.org/packages/be/ae/d44c7d598b66beaea527e6d8d60fd5046d4527e8c59f07de45c9b5577a75/cmdi-2.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "737067e82fa9e1e1a863ad9efd6e41717c31b10ee3aaaa3965491a439cd4be65",
"md5": "4b477b7b5ad17b189341cd01ccd63728",
"sha256": "3c9f6def28d5a97ba881b3eba34efef1a4fd4765184a93128a4d2c76667ee27f"
},
"downloads": -1,
"filename": "cmdi-2.0.0.tar.gz",
"has_sig": false,
"md5_digest": "4b477b7b5ad17b189341cd01ccd63728",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9,<4.0",
"size": 16548,
"upload_time": "2023-11-30T00:41:34",
"upload_time_iso_8601": "2023-11-30T00:41:34.144186Z",
"url": "https://files.pythonhosted.org/packages/73/70/67e82fa9e1e1a863ad9efd6e41717c31b10ee3aaaa3965491a439cd4be65/cmdi-2.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-11-30 00:41:34",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "feluxe",
"github_project": "cmdi",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "cmdi"
}