The context is copied from a parent to a child. In your case context.obj for cli() which you do not expose in your example, is None. The None is copied into the child context for fn1() and fn2(), but then in fn1() you change the copy to 1. Finally, in fn2() you receive a copy of the context for cli() in which obj is still None
So to achieve your desired result there are (at least) two options.
- Make
context.objon the parent context a mutable object, and then mutate said object - As you indicated was possible, directly access the parent context via something like
ctx.parent.obj = 1
An example of the first (my preferred) method:
Example
import click, sys
@click.group(chain=True)
@click.pass_context
def cli(ctx) -> None:
ctx.obj = {}
@cli.command()
@click.pass_context
def fn1(ctx):
ctx.obj['fn1'] = 1
@cli.command()
@click.pass_context
def fn2(ctx):
click.echo(f'obj: {ctx.obj}')
Test Code
if __name__ == "__main__":
print('Click Version: {}'.format(click.__version__))
print('Python Version: {}'.format(sys.version))
print('-----------')
cmd = 'fn1 fn2'
print('> ' + cmd)
cli(cmd.split())
Results:
Click Version: 8.1.3
Python Version: 3.7.8 (tags/v3.7.8:4b47a5b6ba, Jun 28 2020, 08:53:46) [MSC v.1916 64 bit (AMD64)]
-----------
> fn1 fn2
obj: {'fn1': 1}
Answer from Stephen Rauch on Stack OverflowAssemblyAI
assemblyai.com › blog › the-definitive-guide-to-python-click
The Definitive Guide to Python Click
July 28, 2021 - All we have to do to make this command a part of the group we created earlier is decorate our function with the decorator <group name>.command(<command name>). To make sure that this object is able to access context, we also use the pass_context decorator. Now that we have some familiarity with decorators and Python Click, let’s use Click’s functionality to make our own pass decorator.
python - Click: Use another function in chained commands with context object - Stack Overflow
I recently have been using the click package to build command line interfaces which has worked perfectly, so far. Now I got into some trouble when using chained commands in combination with the co... More on stackoverflow.com
python - Multiple context objects in CLI (Click) - Stack Overflow
1 Command line interface with multiple commands using click: add unspecified options for command as dictionary · 4 With python click, how can I avoid duplicating argument code used by multiple subcommands More on stackoverflow.com
python - How can I pass a ctx (Context) to CliRunner? - Stack Overflow
The key part of this answer is ... @click.pass_context decorator and ctx parameter for catching it. ... The first argument should be the the root group of click, then you can pass the command you want to call to the second argument(that is sayhello). ... # !/usr/bin/env python # coding: ... More on stackoverflow.com
python - How to get access to click context in an option decorator? - Stack Overflow
I am trying to create a cli application using Python click and I want to use a function I assigned to click.core.Context.obj as the callback function of an option. The function I use as a callback More on stackoverflow.com
10:47
Build AWESOME CLIs With Click in Python with Subcommands - YouTube
Python Click Part 2 of 4
Creating CLIs in Python with Click
How to Build a Python CLI Tool People Actually Want to Use
14:32
Creating CLIs in Python with Click - YouTube
05:28
Tkinter Context Menus: Adding right-click functionality to your ...
Readthedocs
pocoo-click.readthedocs.io › en › latest › api
API - the Click Documentation
Commands are the basic building block of command line interfaces in Click. A basic command handles command line parsing and might dispatch more parsing to commands nested below it. Changed in version 2.0: Added the context_settings parameter.
ProgramCreek
programcreek.com › python › example › 88034 › click.Context
Python Examples of click.Context
:param click_context: click context object. :param agents: agents names. :param multithreaded: bool flag to run as multithreads. :return: None.
Digitalk
digitalk.tech › blog › python-click-or-how-to-write-professional-cli-applications
Mndwrk Blog - Python click or how to write professional CLI applications
June 17, 2022 - Finally, the ctx parameter is the currently active click execution context (click.Context object). Another interesting thing is the call to self.fail, which will raise a click-specific exception. This exception will then be used to display an error if the input is not a valid JSON file, e.g.: $ echo 'not-a-json' | python3 json_ops.py list-key -k key Usage: json_ops.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]... Try 'json_ops.py --help' for help.
Click Documentation
click.palletsprojects.com › en › stable › complex
Complex Applications — Click Documentation (8.5.x)
Whenever a Click command is executed, a Context object is created which holds state for this particular invocation. It remembers parsed parameters, what command created it, which resources need to be cleaned up at the end of the function, and so forth.
dbader.org
dbader.org › blog › mastering-click-advanced-python-command-line-apps
Mastering Click: Writing Advanced Python Command-Line Apps – dbader.org
January 2, 2018 - What we are doing here is telling Click that we want access to the context of the command (or group) and Click will pass it to our function as the first argument, I called it ctx. In the function itself, we can now set the obj attribute on the context to an empty dictionary that we can then fill with data.
Click Documentation
click.palletsprojects.com › en › stable › commands
Advanced Groups and Context — Click Documentation (8.5.x)
For instance, a common pattern would be to configure an object representing state and then storing it on the context and then to use a custom decorator to find the most recent object of this sort and pass it as first argument. For instance, the pass_obj() decorator can be implemented like this: from functools import update_wrapper def pass_obj(f): @click.pass_context def new_func(ctx, *args, **kwargs): return ctx.invoke(f, ctx.obj, *args, **kwargs) return update_wrapper(new_func, f)
Click Documentation
click.palletsprojects.com › en › stable › advanced
Advanced Patterns — Click Documentation (8.5.x)
It can be useful to open a resource in a group, to be made available to subcommands. Many types of resources need to be closed or otherwise cleaned up after use. The standard way to do this in Python is by using a context manager with the with statement.
Top answer 1 of 3
9
You would directly pass your Config instance as keyword argument obj to runner.invoke:
import click
from click.testing import CliRunner
class Config():
def __init__(self):
self.value = 651
@click.command()
@click.pass_obj
def print_numberinfo(obj):
if not hasattr(obj, 'value'):
obj = Config()
click.echo(obj.value)
def test_print_numberinfo():
obj = Config()
obj.value = 777
runner = CliRunner()
# how do I pass ctx to runner.invoke?
result = runner.invoke(print_numberinfo, obj=obj)
assert result.output == str(obj.value) + '\n'
2 of 3
1
I had the same problem. I wanted to pass a db session from the test side to my command method.
Here's my related code snippets and workaround using @click.pass_context:
command method
import asyncclick as click
import inspect
from getpass import getpass
from . import crud, schemas, database
class Provide:
def __init__(self, value):
self.value = value
def inject_db(f):
sig = inspect.signature(f)
async def wrapper(*args, **kwargs):
for param in sig.parameters.values():
if isinstance(param.default, Provide):
async for db in param.default.value():
kwargs[param.name] = db
await f(*args, **kwargs)
return wrapper
@click.group()
@click.pass_context
async def cli(ctx):
pass
@cli.command(name="create_superuser")
@click.pass_context
@inject_db
async def create_superuser(ctx, db: database.AsyncSession = Provide(database.get_db)):
if ctx.obj is not None:
"""ctx comes from test"""
db = ctx.obj
username = click.prompt("Username", type=str)
email = click.prompt("Email (optional)", type=str, default="")
password = getpass("Password: ")
confirm_password = getpass("Confirm Password: ")
if password != confirm_password:
click.echo("Passwords do not match")
return
user = schemas.UserAdminCreate(
username=username,
email=None if not email else email,
password=password,
role="admin",
)
await crud.create_user(db=db, user=user)
if __name__ == "__main__":
cli()
cli.add_command(create_superuser)
test part
@pytest.mark.asyncio
@patch("ai3d.commands.click.prompt", side_effect=["testuser", "[email protected]"])
@patch("ai3d.commands.getpass", side_effect=["testpassword", "testpassword"])
async def test_create_superuser(
mock_getpass, mock_prompt, mock_get_hashed_password, db: AsyncSession
):
with mock_get_hashed_password:
runner = CliRunner()
result = await runner.invoke(commands.create_superuser, obj=db)
assert result.exit_code == 0
mock_prompt.assert_any_call("Username", type=str)
mock_prompt.assert_any_call("Email (optional)", type=str, default="")
mock_getpass.assert_any_call("Password: ")
mock_getpass.assert_any_call("Confirm Password: ")
user = await crud.get_user(db=db, username="testuser")
assert user.username == "testuser"
assert user.email == "[email protected]"
assert user.password == "hashedtestpassword"
assert user.role == "admin"
[NOTE]:
- I should add that in my case I'm using the
asynclickpackage which is a fork ofclickproviding async stuff, that's why I usedasync-awiatwithin the code. - The key part of this answer is passing the object through the
obj=parameter inCliRunner().invoke()method and using@click.pass_contextdecorator andctxparameter for catching it.
Readthedocs
pocoo-click.readthedocs.io › en › latest › advanced
Advanced Patterns - the Click Documentation
Starting with Click 5.0 it is possible to access the current context from anywhere within the same thread through the use of the get_current_context() function which returns it. This is primarily useful for accessing the context bound object as well as some flags that are stored on it to customize ...
Click Documentation
click.palletsprojects.com › en › stable › commands-and-groups
Basic Commands, Groups, Context — Click Documentation (8.5.x)
Starting with Click 5.0 it is possible to access the current context from anywhere within the same thread through the use of the get_current_context() function which returns it. This is primarily useful for accessing the context bound object as well as some flags that are stored on it to customize ...
Blue Book
lyz-code.github.io › blue-book › coding › python › click
Click - The Blue Book
Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It’s the “Command Line Interface Creation Kit”. It’s highly configurable but comes with sensible defaults out of the box · Click has the following features:
Paiml
paiml.com › docs › home › books › python-command-line-tools › chapter01-getting-started-click
Chapter01 Getting Started Click | Pragmatic AI Labs and Solutions
The new decorator starts with the name of the function of the group (main() in this case is @main.command()). Finally, the context is requested with @click.pass_context and declared as an argument (ctx). This context is what allows the function to print the help menu from the parent command (in main()), instead of generating some other help menu. Run this once again to see how it behaves with the new sub-command: $ python cli.py help Usage: cli.py [OPTIONS] COMMAND [ARGS]... Options: -h, --help Show this message and exit.