Comments (19)
Alright, great! Thank you for your work!
from cyclopts.
This feature is implemented in the newly released v2.7.0. See the example in the docs and the API docs for usage.
from cyclopts.
@nordmtr fyi
from cyclopts.
I think we could/should support something like this! Does typer do any auto-namespacing for this? Or are all parameters just directly derived, regardless of the command hierarchy? In your example you have MY_APP_MY_ARG
, I'm assuming we would not want SEE EDIT BELOW.MY_APP_FOO_MY_ARG
, right?
What do we think about this proposed API:
app = App(
default_parameter=Parameter(auto_env_var_prefix="MY_APP_"),
)
Design decisions and behavior:
- I think this configuration belongs in
Parameter
; it naturally lets the configuration inheritance mechanism do its thing. This also places it nicely next toParameter.env_var
. - If both
auto_env_var_prefix
andenv_var
are supplied (either directly, or implicitly through configuration inheritance),env_var
gets priority andauto_env_var_prefix
is effectively ignored. - The environment variable name will be derived something like:
cparam: cyclopts.Parameter # Fully configured at this point
env_var = tuple(cparam.auto_env_var_prefix + cparam.name_transform(name).replace("-","_")upper() for name in cparam.name)
-
If multiple explicitly specified long
Parameter.name
are provided, all of them will be used and multiple automatic env vars will be allowed. This will follow the rules ofenv_var
. -
Default value of
auto_env_var_prefix: Optional[str]
will beNone
(deactivated). An empty string is a valid value to enable the feature, resulting in env vars without a prefix likeMY_ARG
.
Misc open-ended questions:
-
Any thoughts/opinions on the above?
-
Do we want to call it
auto_env_var_prefix
, or one of {env_var_auto_prefix
,env_var_prefix_auto
}. Benefits of the former is that it reads nicely and directly mirrors Typer. Benefits of the latter are that it makes all theenv_var
attributes start withenv_var
, which helps keeps things a bit more organized.
EDIT: Sorry, just noticed your comment in your initial post:
Nested commands should insert their names in the generated env variable.
So then to confirm, the following command:
@app.command
def foo(my_arg: int):
print(my_arg)
Should actually have env var MY_APP_FOO_MY_ARG
, correct?
Something like
@app.default # NOTICE: DEFAULT
def foo(my_arg: int):
print(my_arg)
will result in MY_APP_MY_ARG
.
from cyclopts.
- Agree
- I think we should do a union instead, it's bad to accidentally confuse the user by not utilizing some of the provided variables. We can just combine both options and check them all.
This pattern empowers having obvious environment variables for all parameters with clear naming, but also doing aliases for parameters with complex, long or unreadable names. - Agree
- Agree
- Agree
Re: naming - I don't think we are bound to Typer's naming.
env_var_auto_prefix
fits the rest of the parameters better.
Re: edit - correct
from cyclopts.
Can you elaborate on 2? I might be a bit confused.
Regardless, I can begin working on this once I'm done with #165
from cyclopts.
So actually, this might play super nicely with #165 as a config. It would look something like:
app = cyclopts.App(
# Multiple configs are allowed, incase you also want to use a toml or something.
config=cyclopts.config.Env("MY_APP_", command_namespace=True),
)
from cyclopts.
I mean that if an env_var
is set in both Parameter
and App
level, the argument should try to read it's value from both environment variables. From both MY_APP_MY_ARG
and MY_CUSTOM_ENV_VARIABLE
.
from cyclopts.
oh gotcha! Yeah I think that makes sense. The Parameter.env_var
will be attempted to be read from first, then the automatic ones.
I almost have #165 in a good state, I'll be going with the approach mentioned above. I should have something in a few days.
from cyclopts.
Just an update on this: I haven't forgotten about it. I've been moving and that's been sucking up a lot of my time 😅
from cyclopts.
Completely understand, moving right now myself, can relate
from cyclopts.
Hey @BrianPugh, thanks for implementing this!
I have just a small question.
I just took a look at the docs and realized the current implementation of this feature is mutually exclusive with the .toml
config.
I wonder if it's necessarily should be the case? Shouldn't the user be able to use both at the same time? I mean defining some arguments in the .toml
config, and some with environment variables (they should take precedence).
from cyclopts.
you can! simply provide a list of callables to config
. E.g.:
app = cyclopts.App(
name="character-counter",
config=[
cyclopts.config.Env(
"CHAR_COUNTER_", # Every environment variable will begin with this.
),
cyclopts.config.Toml(
"pyproject.toml", # Name of the TOML File
root_keys=["tool", "character-counter"], # The project's namespace in the TOML.
# If "pyproject.toml" is not found in the current directory,
# then iteratively search parenting directories until found.
search_parents=True,
),
],
)
Here the environment variables will get precedence since they are defined earlier in the config
list.
from cyclopts.
Oh, thanks, didn't realize this was a thing.
from cyclopts.
I just spent about 20 minutes trying to figure out why this wasn't working and I think this is the answer, but it's not very intuitive.
What I was trying to do was something like this:
app = App(config=cyclopts.config.Env("OTF_"))
OPT_TOKENS = Annotated[str, Parameter(show=False, allow_leading_hyphen=True)]
OPT_USERNAME = Annotated[str, Parameter(help="Username for the OTF API")]
OPT_PASSWORD = Annotated[str, Parameter(help="Password for the OTF API")]
OPT_LOG_LEVEL = Annotated[str, Parameter("CRITICAL", help="Log level", env_var="LOG_LEVEL")]
@app.meta.default
async def launcher(*tokens: OPT_TOKENS, username: OPT_USERNAME, password: OPT_PASSWORD):
api = await Api.create(username, password)
command, bound = app.parse_args(tokens)
return command(*bound.args, **bound.kwargs, api=api)
@app.command
def test(*,api:Annotated[Api,Parameter(parse=False)]):
print("test")
But it seems multiple things go wrong here. This doesn't seem to work with meta
, as far as I can tell. It also doesn't seem to work with default
, regardless if it's meta or not. I've only gotten it to work by changing it to @app.command.
I got this to work the way I wanted eventually by just setting the env_var
on the parameter directly, like env_var=["OTF_EMAIL","OTF_USERNAME"]
, and that works in the meta and in the default, without issue. I think this may need to be clearer in the documentation.
from cyclopts.
investigating; thanks for the report!
from cyclopts.
Quickly testing the following python script:
import cyclopts
from cyclopts import App
app = App(config=cyclopts.config.Env("OTF_"))
@app.default
def main(foo="python-default"):
print(foo)
if __name__ == "__main__":
app()
$ python issue171.py
python-default
$ python issue171.py cli-value
cli-value
$ OTF_FOO=env-value python issue171.py
env-value
So it certainly works with @app.default
. However, I am running into issues with meta. Investigating deeper.
from cyclopts.
Opened #184 which should address the unintuitive behavior.
from cyclopts.
Bit of unsolicited advice, but I'd recommend using docstrings to minimize the number of parameters you need to Annotated
:
import cyclopts
from cyclopts import App, Parameter
from typing import Annotated
app = App(config=cyclopts.config.Env("OTF_"))
@app.meta.default
async def launcher(
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
username: str,
password: str,
):
"""My OTF CLI.
Parameters
----------
username: str
Username for the OTF API.
password: str
Password for the OTF API.
"""
print(f"{username=} {password=}")
command, bound = app.parse_args(tokens)
return command(*bound.args, **bound.kwargs, api=None)
@app.command
def test(*, api: Annotated[str, Parameter(parse=False)]):
print("test")
if __name__ == "__main__":
app.meta()
from cyclopts.
Related Issues (20)
- App() name parameter type hint adjustment HOT 2
- Docstring hyperlink HOT 11
- [Feature request]: Nested pydantic validation HOT 14
- [Discussion] First-class config overrides via pyproject.toml? HOT 2
- [Feature Request] Completion? HOT 6
- App() console parameter HOT 2
- integration with https://pypi.org/project/typed-settings/ HOT 2
- [BUG]: bad error report because of assetion error when there is UnusedCliTokensError HOT 2
- [BUG] Weird behavior of list of tuples HOT 7
- [BUG] list of tuple of single item is parsed but take only the first character of each token get parsed HOT 2
- Misleading error message when flag name has an underscore HOT 2
- the help format parsing is not consistent HOT 2
- Misc likely (rare) typing bugs HOT 1
- Use of the meta application results in a double help display HOT 5
- Automatic conversion from underscore to hyphen HOT 3
- Prompt for missing required parameters HOT 6
- Newlines should not be present in descriptions that span multiple lines HOT 5
- Importing cyclopts fails if there is a file called "tokenize.py" in the project which imports cyclopts HOT 1
- [feature request]: automatic markdown documentation generation HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from cyclopts.