Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

to_tap_class, and inspect fields instead of signature for data models #128

Merged
merged 40 commits into from
Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7c454c7
[WIP] Tap class from Pydantic model or dataclass
kddubey Jan 2, 2024
420c1ff
use str for Any, fix docstring
kddubey Jan 3, 2024
9ace3f6
Remove comments about refactor
kddubey Jan 3, 2024
ea6c046
add pydantic as a dependency so tests run
martinjm97 Jan 13, 2024
d44f48e
use older verions
martinjm97 Jan 13, 2024
2d92219
use typing type
martinjm97 Jan 13, 2024
603c6ad
convert_to_tap_class
kddubey Jan 18, 2024
27df903
add dev extra
kddubey Jan 18, 2024
89123b7
fix desc for known_only
kddubey Jan 18, 2024
0b4f202
dont require pydantic
kddubey Jan 19, 2024
a11a088
fix docstring extraction
kddubey Jan 19, 2024
c0d226f
test pydantic BaseModel
kddubey Jan 19, 2024
0f4df97
test pydantic dataclass
kddubey Jan 19, 2024
53eb203
basic test convert_to_tap_class
kddubey Jan 19, 2024
9163e5c
add test todos
kddubey Jan 19, 2024
c8ff11d
dict -> Dict
kddubey Jan 19, 2024
64820af
rename convert_to_tap_class -> to_tap_class
kddubey Jan 19, 2024
99ea551
lingering pipe
kddubey Jan 19, 2024
1b28e87
fix comment
kddubey Jan 19, 2024
0dcb864
test more complex subclasser
kddubey Jan 20, 2024
d399370
update demo
kddubey Jan 20, 2024
43d0d4f
std docstrings
kddubey Jan 20, 2024
368dcf7
test help message
kddubey Jan 20, 2024
0efc236
test arg_list optional
kddubey Jan 20, 2024
5dbe6dd
no func_kwargs for to_tap_class
kddubey Jan 20, 2024
e46120c
fix for python<=3.10 methinks
kddubey Jan 20, 2024
bda73c0
pydantic v1 wackiness
kddubey Jan 20, 2024
978b97d
fix for py39 argparse
kddubey Jan 20, 2024
97f1b6c
add to readme
kddubey Jan 21, 2024
c2c7087
test subparsing
kddubey Jan 23, 2024
76a68f7
test SystemExit error message
kddubey Jan 23, 2024
15eaf3b
stdout -> stderr for non-help
kddubey Jan 23, 2024
f38fba7
dont require pydantic for tests to run
kddubey Jan 23, 2024
878162a
dont require pydantic for test workflow
kddubey Jan 23, 2024
48d237c
unused field
kddubey Jan 23, 2024
0c439f6
use type_to_str instead
kddubey Jan 23, 2024
d83bc1d
little things
kddubey Jan 23, 2024
3523da2
add general pattern
kddubey Feb 1, 2024
28e3c19
littler things
kddubey Feb 19, 2024
7461ce7
allow extra args for Pydantic BaseModels with extra=allow
kddubey Feb 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
git config --global user.name "Your Name"
python -m pip install --upgrade pip
python -m pip install flake8 pytest
python -m pip install -e .
python -m pip install -e ".[dev]"
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Expand Down
68 changes: 68 additions & 0 deletions demo_data_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""
Example:

python demo_data_model.py \
--arg_str test \
--arg_list x y z \
--arg_bool \
-arg 2
"""
from pydantic import BaseModel, Field
from tap import tapify, convert_to_tap_class


class Model(BaseModel):
"""
My Pydantic Model which contains script args.
"""

arg_str: str = Field(description="hello")
arg_bool: bool = Field(default=True, description=None)
arg_list: list[str] | None = Field(default=None, description="optional list")


def main(model: Model) -> None:
print("Parsed args into Model:")
print(model)


def to_number(string: str) -> float | int:
return float(string) if "." in string else int(string)


class ModelTap(convert_to_tap_class(Model)):
# You can supply additional arguments here
argument_with_really_long_name: float | int = 3
"This argument has a long name and will be aliased with a short one"

def configure(self) -> None:
# You can still add special argument behavior
self.add_argument("-arg", "--argument_with_really_long_name", type=to_number)

def process_args(self) -> None:
# You can still validate and modify arguments
# (You should do this in the Pydantic Model. I'm just demonstrating that this functionality is still possible)
if self.argument_with_really_long_name > 4:
raise ValueError("nope")

# No auto-complete (and other niceties) for the super class attributes b/c this is a dynamic subclass. Sorry
if self.arg_bool:
self.arg_str += " processed"


if __name__ == "__main__":
# You don't have to subclass tap_class_from_data_model(Model) if you just want a plain argument parser:
# ModelTap = tap_class_from_data_model(Model)
args = ModelTap(description="Script description").parse_args()
print("Parsed args:")
print(args)
# Run the main function. Pydantic BaseModels ignore arguments which aren't one of their fields instead of raising an
# error
model = Model(**args.as_dict())
main(model)


# This works but doesn't show the field description, and immediately returns a Model instance instead of a Tap class
# if __name__ == "__main__":
# model = tapify(Model)
# print(model)
8 changes: 7 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
with open("README.md", encoding="utf-8") as f:
long_description = f.read()

test_requirements = [
"pydantic >= 2.5.0",
"pytest",
]

setup(
name="typed-argument-parser",
version=__version__,
Expand All @@ -26,7 +31,8 @@
packages=find_packages(),
package_data={"tap": ["py.typed"]},
install_requires=["typing-inspect >= 0.7.1", "docstring-parser >= 0.15"],
tests_require=["pytest"],
tests_require=test_requirements,
kddubey marked this conversation as resolved.
Show resolved Hide resolved
extras_require={"dev": test_requirements},
python_requires=">=3.8",
classifiers=[
"Programming Language :: Python :: 3",
Expand Down
11 changes: 9 additions & 2 deletions tap/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from argparse import ArgumentError, ArgumentTypeError
from tap._version import __version__
from tap.tap import Tap
from tap.tapify import tapify
from tap.tapify import tapify, convert_to_tap_class

__all__ = ["ArgumentError", "ArgumentTypeError", "Tap", "tapify", "__version__"]
__all__ = [
"ArgumentError",
"ArgumentTypeError",
"Tap",
"tapify",
"convert_to_tap_class",
"__version__",
]
Loading