Compare commits

..

9 Commits

Author SHA1 Message Date
9d2e2afede Add failing reproduction test case 2022-06-07 14:50:56 +02:00
418b12bbd6 Cleanup 2022-06-07 14:31:15 +02:00
ecff53f2d5 Have naked $ make list all tasks 2022-06-07 14:29:19 +02:00
41da87f7c8 Install .[test] reqs in make install-reqs 2022-06-07 14:26:48 +02:00
4f172a61b4 Fix installation 2022-06-07 14:23:52 +02:00
542a2d35de Fix typos in comment lines (#1405)
* httpie/internal/daemons.py
* httpie/utils.py
2022-05-19 16:22:50 +03:00
d9e1dc08c9 Package man pages into the deb packages as well. (#1403) 2022-05-16 18:19:49 +03:00
3b734fb0bc Fix a misput backtick 2022-05-16 10:10:51 +03:00
8abe47969e Improve single-binary method wording (#1399) 2022-05-10 19:55:31 +03:00
20 changed files with 247 additions and 202 deletions

View File

@ -59,8 +59,10 @@ $ git checkout -b my_topical_branch
#### Setup
The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started. Just run
the following command, which:
The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started.
You can run `$ make` to see all the available tasks.
To get started, run the command below, which:
- Creates an isolated Python virtual environment inside `./venv`
(via the standard library [venv](https://docs.python.org/3/library/venv.html) tool);
@ -70,7 +72,7 @@ the following command, which:
- and runs tests (It is the same as running `make install test`).
```bash
$ make
$ make all
```
#### Python virtual environment

View File

@ -22,6 +22,26 @@ VENV_PYTHON=$(VENV_BIN)/python
export PATH := $(VENV_BIN):$(PATH)
default: list-tasks
###############################################################################
# Default task to get a list of tasks when `make' is run without args.
# <https://stackoverflow.com/questions/4219255>
###############################################################################
list-tasks:
@echo Available tasks:
@echo ----------------
@$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'
@echo
###############################################################################
# Installation
###############################################################################
all: uninstall-httpie install test
@ -33,7 +53,7 @@ install-reqs:
$(VENV_PIP) install --upgrade pip wheel build
@echo $(H1)Installing dev requirements$(H1END)
$(VENV_PIP) install --upgrade --editable '.[dev]'
$(VENV_PIP) install --upgrade '.[dev]' '.[test]'
@echo $(H1)Installing HTTPie$(H1END)
$(VENV_PIP) install --upgrade --editable .

View File

@ -217,7 +217,7 @@ $ pacman -Syu
#### Single binary executables
Have a standalone HTTPie executable when you don't want to go through the full installation process
Get the standalone HTTPie Linux executables when you don't want to go through the full installation process
```bash
# Install httpie
@ -2425,7 +2425,7 @@ You can check whether a new update is available for your system by running `http
In the past `pip` was used to install/uninstall plugins, but on some environments (e.g., brew installed
packages) it wasnt working properly. The new interface is a very simple overlay on top of `pip` to allow
plugin installations on every installation method.
plugin installations on every installation method.
By default, the plugins (and their missing dependencies) will be stored under the configuration directory,
but this can be modified through `plugins_dir` variable on the config.

View File

@ -17,13 +17,13 @@ docs-structure:
Windows:
- chocolatey
Linux:
- snap-linux
- brew-linux
- apt
- dnf
- yum
- pacman
- single-binary
- snap-linux
- brew-linux
- pacman
FreeBSD:
- pkg
@ -186,11 +186,12 @@ tools:
single-binary:
title: Single binary executables
name: Single binary executables
note: Have a standalone HTTPie executable when you don't want to go through the full installation process.
note: Get the standalone HTTPie Linux executables when you don't want to go through the full installation process.
links:
commands:
install:
- https --download packages.httpie.io/binaries/linux/http-latest -o http
- chmod +x ./http
- ln -ls ./http ./https
- chmod +x ./http ./https
upgrade:
- https --download packages.httpie.io/binaries/linux/http-latest -o http

View File

@ -6,6 +6,9 @@ from typing import Iterator, Tuple
BUILD_DIR = Path(__file__).parent
HTTPIE_DIR = BUILD_DIR.parent.parent.parent
EXTRAS_DIR = HTTPIE_DIR / 'extras'
MAN_PAGES_DIR = EXTRAS_DIR / 'man'
SCRIPT_DIR = BUILD_DIR / Path('scripts')
HOOKS_DIR = SCRIPT_DIR / 'hooks'
@ -50,6 +53,11 @@ def build_packages(http_binary: Path, httpie_binary: Path) -> None:
(http_binary, '/usr/bin/https'),
(httpie_binary, '/usr/bin/httpie'),
]
files.extend(
(man_page, f'/usr/share/man/man1/{man_page.name}')
for man_page in MAN_PAGES_DIR.glob('*.1')
)
# A list of additional dependencies
deps = [
'python3 >= 3.7',

View File

@ -132,10 +132,3 @@ class RequestType(enum.Enum):
FORM = enum.auto()
MULTIPART = enum.auto()
JSON = enum.auto()
EMPTY_STRING = ''
OPEN_BRACKET = '['
CLOSE_BRACKET = ']'
BACKSLASH = '\\'
HIGHLIGHTER = '^'

View File

@ -9,8 +9,14 @@ from typing import (
Type,
Union,
)
from httpie.cli.dicts import NestedJSONArray
from httpie.cli.constants import EMPTY_STRING, OPEN_BRACKET, CLOSE_BRACKET, BACKSLASH, HIGHLIGHTER
from .dicts import NestedJSONArray
EMPTY_STRING = ''
HIGHLIGHTER = '^'
OPEN_BRACKET = '['
CLOSE_BRACKET = ']'
BACKSLASH = '\\'
class HTTPieSyntaxError(ValueError):
@ -31,7 +37,7 @@ class HTTPieSyntaxError(ValueError):
if self.token is not None:
lines.append(self.source)
lines.append(
' ' * (self.token.start)
' ' * self.token.start
+ HIGHLIGHTER * (self.token.end - self.token.start)
)
return '\n'.join(lines)
@ -51,9 +57,15 @@ class TokenKind(Enum):
return 'a ' + self.name.lower()
OPERATORS = {OPEN_BRACKET: TokenKind.LEFT_BRACKET, CLOSE_BRACKET: TokenKind.RIGHT_BRACKET}
OPERATORS = {
OPEN_BRACKET: TokenKind.LEFT_BRACKET,
CLOSE_BRACKET: TokenKind.RIGHT_BRACKET,
}
SPECIAL_CHARS = OPERATORS.keys() | {BACKSLASH}
LITERAL_TOKENS = [TokenKind.TEXT, TokenKind.NUMBER]
LITERAL_TOKENS = [
TokenKind.TEXT,
TokenKind.NUMBER,
]
class Token(NamedTuple):

View File

@ -13,7 +13,8 @@ import urllib3
from . import __version__
from .adapters import HTTPieHTTPAdapter
from .context import Environment
from .cli.constants import EMPTY_STRING, HTTP_OPTIONS
from .cli.constants import HTTP_OPTIONS
from .cli.nested_json import EMPTY_STRING
from .cli.dicts import HTTPHeadersDict, NestedJSONArray
from .encoding import UTF8
from .models import RequestsMessage

View File

@ -18,7 +18,7 @@ from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError
from .encoding import UTF8
from .utils import repr_dict
from .output.ui.palette import RichColor
from .output.ui.palette import GenericColor
if TYPE_CHECKING:
from rich.console import Console
@ -31,9 +31,9 @@ class LogLevel(str, Enum):
LOG_LEVEL_COLORS = {
LogLevel.INFO: RichColor.PINK,
LogLevel.WARNING: RichColor.ORANGE,
LogLevel.ERROR: RichColor.RED,
LogLevel.INFO: GenericColor.PINK,
LogLevel.WARNING: GenericColor.ORANGE,
LogLevel.ERROR: GenericColor.RED,
}
LOG_LEVEL_DISPLAY_THRESHOLDS = {
@ -191,10 +191,10 @@ class Environment:
force_terminal: bool
) -> 'Console':
from rich.console import Console
from httpie.output.ui.palette import make_rich_theme_from_style
from httpie.output.ui.rich_palette import _make_rich_color_theme
style = getattr(self.args, 'style', None)
theme = make_rich_theme_from_style(style)
theme = _make_rich_color_theme(style)
# Rich infers the rest of the knowledge (e.g encoding)
# dynamically by looking at the file/stderr.
return Console(

View File

@ -1,6 +1,6 @@
"""
This module provides an interface to spawn a detached task to be
runned with httpie.internal.daemon_runner on a separate process. It is
run with httpie.internal.daemon_runner on a separate process. It is
based on DVC's daemon system.
https://github.com/iterative/dvc/blob/main/dvc/daemon.py
"""

View File

@ -1,12 +1,18 @@
from enum import Enum
from typing import Optional
from httpie.output.ui.palette.utils import ColorString
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Optional, List
PYGMENTS_BRIGHT_BLACK = 'ansibrightblack'
AUTO_STYLE = 'auto' # Follows terminal ANSI color styles
class Styles(Enum):
PIE = auto()
ANSI = auto()
class PieStyle(str, Enum):
UNIVERSAL = 'pie'
DARK = 'pie-dark'
@ -18,11 +24,34 @@ PIE_STYLE_TO_SHADE = {
PieStyle.UNIVERSAL: '600',
PieStyle.LIGHT: '700',
}
SHADE_TO_PIE_STYLE = {
shade: style for style, shade in PIE_STYLE_TO_SHADE.items()
}
class ColorString(str):
def __or__(self, other: str) -> 'ColorString':
"""Combine a style with a property.
E.g: PieColor.BLUE | BOLD | ITALIC
"""
if isinstance(other, str):
# In case of PieColor.BLUE | SOMETHING
# we just create a new string.
return ColorString(self + ' ' + other)
elif isinstance(other, GenericColor):
# If we see a GenericColor, then we'll wrap it
# in with the desired property in a different class.
return _StyledGenericColor(other, styles=self.split())
elif isinstance(other, _StyledGenericColor):
# And if it is already wrapped, we'll just extend the
# list of properties.
other.styles.extend(self.split())
return other
else:
return NotImplemented
class PieColor(ColorString, Enum):
"""Styles that are available only in Pie themes."""
@ -42,6 +71,42 @@ class PieColor(ColorString, Enum):
YELLOW = 'yellow'
class GenericColor(Enum):
"""Generic colors that are safe to use everywhere."""
# <https://rich.readthedocs.io/en/stable/appendix/colors.html>
WHITE = {Styles.PIE: PieColor.WHITE, Styles.ANSI: 'white'}
BLACK = {Styles.PIE: PieColor.BLACK, Styles.ANSI: 'black'}
GREEN = {Styles.PIE: PieColor.GREEN, Styles.ANSI: 'green'}
ORANGE = {Styles.PIE: PieColor.ORANGE, Styles.ANSI: 'yellow'}
YELLOW = {Styles.PIE: PieColor.YELLOW, Styles.ANSI: 'bright_yellow'}
BLUE = {Styles.PIE: PieColor.BLUE, Styles.ANSI: 'blue'}
PINK = {Styles.PIE: PieColor.PINK, Styles.ANSI: 'bright_magenta'}
PURPLE = {Styles.PIE: PieColor.PURPLE, Styles.ANSI: 'magenta'}
RED = {Styles.PIE: PieColor.RED, Styles.ANSI: 'red'}
AQUA = {Styles.PIE: PieColor.AQUA, Styles.ANSI: 'cyan'}
GREY = {Styles.PIE: PieColor.GREY, Styles.ANSI: 'bright_black'}
def apply_style(
self, style: Styles, *, style_name: Optional[str] = None
) -> str:
"""Apply the given style to a particular value."""
exposed_color = self.value[style]
if style is Styles.PIE:
assert style_name is not None
shade = PIE_STYLE_TO_SHADE[PieStyle(style_name)]
return get_color(exposed_color, shade)
else:
return exposed_color
@dataclass
class _StyledGenericColor:
color: 'GenericColor'
styles: List[str] = field(default_factory=list)
# noinspection PyDictCreation
COLOR_PALETTE = {
# Copy the brand palette
@ -187,6 +252,10 @@ COLOR_PALETTE.update(
)
def boldify(color: PieColor) -> str:
return f'bold {color}'
# noinspection PyDefaultArgument
def get_color(
color: PieColor, shade: str, *, palette=COLOR_PALETTE

View File

@ -1,3 +0,0 @@
from httpie.output.ui.palette.pie import AUTO_STYLE, SHADE_TO_PIE_STYLE, PieStyle, PieColor, ColorString, get_color # noqa
from httpie.output.ui.palette.rich import RichColor, make_rich_theme_from_style
from httpie.output.ui.palette.utils import ColorString

View File

@ -1,18 +0,0 @@
from httpie.output.ui.palette.rich import RichColor
from httpie.output.ui.palette.utils import ColorString
RICH_BOLD = ColorString('bold')
# Rich-specific color code declarations
# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py>
RICH_CUSTOM_STYLES = {
'progress.description': RICH_BOLD | RichColor.WHITE,
'progress.data.speed': RICH_BOLD | RichColor.GREEN,
'progress.percentage': RICH_BOLD | RichColor.AQUA,
'progress.download': RICH_BOLD | RichColor.AQUA,
'progress.remaining': RICH_BOLD | RichColor.ORANGE,
'bar.complete': RICH_BOLD | RichColor.PURPLE,
'bar.finished': RICH_BOLD | RichColor.GREEN,
'bar.pulse': RICH_BOLD | RichColor.PURPLE,
'option': RICH_BOLD | RichColor.PINK,
}

View File

@ -1,111 +0,0 @@
from collections import ChainMap
from typing import TYPE_CHECKING, Any, Optional, List
from enum import Enum, auto
from dataclasses import dataclass, field
if TYPE_CHECKING:
from rich.theme import Theme
from httpie.output.ui.palette.pie import (
PIE_STYLE_TO_SHADE,
PieStyle,
PieColor,
get_color,
) # noqa
class RichTheme(Enum):
"""Represents the color theme to use within rich."""
PIE = auto()
ANSI = auto()
@classmethod
def from_style_name(cls, style_name: str) -> 'RichTheme':
try:
PieStyle(style_name)
except ValueError:
return RichTheme.ANSI
else:
return RichTheme.PIE
class RichColor(Enum):
"""Generic colors that are safe to use everywhere within rich."""
# <https://rich.readthedocs.io/en/stable/appendix/colors.html>
WHITE = {RichTheme.PIE: PieColor.WHITE, RichTheme.ANSI: 'white'}
BLACK = {RichTheme.PIE: PieColor.BLACK, RichTheme.ANSI: 'black'}
GREEN = {RichTheme.PIE: PieColor.GREEN, RichTheme.ANSI: 'green'}
ORANGE = {RichTheme.PIE: PieColor.ORANGE, RichTheme.ANSI: 'yellow'}
YELLOW = {RichTheme.PIE: PieColor.YELLOW, RichTheme.ANSI: 'bright_yellow'}
BLUE = {RichTheme.PIE: PieColor.BLUE, RichTheme.ANSI: 'blue'}
PINK = {RichTheme.PIE: PieColor.PINK, RichTheme.ANSI: 'bright_magenta'}
PURPLE = {RichTheme.PIE: PieColor.PURPLE, RichTheme.ANSI: 'magenta'}
RED = {RichTheme.PIE: PieColor.RED, RichTheme.ANSI: 'red'}
AQUA = {RichTheme.PIE: PieColor.AQUA, RichTheme.ANSI: 'cyan'}
GREY = {RichTheme.PIE: PieColor.GREY, RichTheme.ANSI: 'bright_black'}
def apply_theme(
self, style: RichTheme, *, style_name: Optional[str] = None
) -> str:
"""Apply the given style to a particular value."""
exposed_color = self.value[style]
if style is RichTheme.PIE:
assert style_name is not None
shade = PIE_STYLE_TO_SHADE[PieStyle(style_name)]
return get_color(exposed_color, shade)
else:
return exposed_color
@dataclass
class _StyledRichColor:
color: RichColor
styles: List[str] = field(default_factory=list)
class _RichColorCaster(dict):
"""
Translate RichColor to a regular string on the attribute access
phase.
"""
def _translate(self, key: Any) -> Any:
if isinstance(key, RichColor):
return key.name.lower()
else:
return key
def __getitem__(self, key: Any) -> Any:
return super().__getitem__(self._translate(key))
def get(self, key: Any) -> Any:
return super().get(self._translate(key))
def make_rich_theme_from_style(style_name: Optional[str] = None) -> 'Theme':
from rich.style import Style
from rich.theme import Theme
from httpie.output.ui.palette.custom_styles import RICH_CUSTOM_STYLES
rich_theme = RichTheme.from_style_name(style_name)
theme = Theme()
for color, color_set in ChainMap(
RichColor.__members__, RICH_CUSTOM_STYLES
).items():
if isinstance(color_set, _StyledRichColor):
properties = dict.fromkeys(color_set.styles, True)
color_set = color_set.color
else:
properties = {}
theme.styles[color.lower()] = Style(
color=color_set.apply_theme(rich_theme, style_name=style_name),
**properties,
)
# E.g translate RichColor.BLUE into blue on key access
theme.styles = _RichColorCaster(theme.styles)
return theme

View File

@ -1,23 +0,0 @@
class ColorString(str):
def __or__(self, other: str) -> 'ColorString':
"""Combine a style with a property.
E.g: PieColor.BLUE | BOLD | ITALIC
"""
from httpie.output.ui.palette.rich import RichColor, _StyledRichColor
if isinstance(other, str):
# In case of PieColor.BLUE | SOMETHING
# we just create a new string.
return ColorString(self + ' ' + other)
elif isinstance(other, RichColor):
# If we see a GenericColor, then we'll wrap it
# in with the desired property in a different class.
return _StyledRichColor(other, styles=self.split())
elif isinstance(other, _StyledRichColor):
# And if it is already wrapped, we'll just extend the
# list of properties.
other.styles.extend(self.split())
return other
else:
return NotImplemented

View File

@ -10,17 +10,17 @@ from rich.text import Text
from httpie.cli.constants import SEPARATOR_GROUP_ALL_ITEMS
from httpie.cli.options import Argument, ParserSpec, Qualifiers
from httpie.output.ui.palette import RichColor
from httpie.output.ui.palette import GenericColor
SEPARATORS = '|'.join(map(re.escape, SEPARATOR_GROUP_ALL_ITEMS))
STYLE_METAVAR = RichColor.YELLOW
STYLE_SWITCH = RichColor.GREEN
STYLE_PROGRAM_NAME = RichColor.GREEN # .boldify()
STYLE_USAGE_OPTIONAL = RichColor.GREY
STYLE_USAGE_REGULAR = RichColor.WHITE
STYLE_USAGE_ERROR = RichColor.RED
STYLE_USAGE_MISSING = RichColor.YELLOW
STYLE_METAVAR = GenericColor.YELLOW
STYLE_SWITCH = GenericColor.GREEN
STYLE_PROGRAM_NAME = GenericColor.GREEN # .boldify()
STYLE_USAGE_OPTIONAL = GenericColor.GREY
STYLE_USAGE_REGULAR = GenericColor.WHITE
STYLE_USAGE_ERROR = GenericColor.RED
STYLE_USAGE_MISSING = GenericColor.YELLOW
STYLE_BOLD = 'bold'
MAX_CHOICE_CHARS = 80

View File

@ -0,0 +1,73 @@
from collections import ChainMap
from typing import TYPE_CHECKING, Any, Optional
if TYPE_CHECKING:
from rich.theme import Theme
from httpie.output.ui.palette import GenericColor, PieStyle, Styles, ColorString, _StyledGenericColor # noqa
RICH_BOLD = ColorString('bold')
# Rich-specific color code declarations
# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py>
CUSTOM_STYLES = {
'progress.description': RICH_BOLD | GenericColor.WHITE,
'progress.data.speed': RICH_BOLD | GenericColor.GREEN,
'progress.percentage': RICH_BOLD | GenericColor.AQUA,
'progress.download': RICH_BOLD | GenericColor.AQUA,
'progress.remaining': RICH_BOLD | GenericColor.ORANGE,
'bar.complete': RICH_BOLD | GenericColor.PURPLE,
'bar.finished': RICH_BOLD | GenericColor.GREEN,
'bar.pulse': RICH_BOLD | GenericColor.PURPLE,
'option': RICH_BOLD | GenericColor.PINK,
}
class _GenericColorCaster(dict):
"""
Translate GenericColor to a regular string on the attribute access
phase.
"""
def _translate(self, key: Any) -> Any:
if isinstance(key, GenericColor):
return key.name.lower()
else:
return key
def __getitem__(self, key: Any) -> Any:
return super().__getitem__(self._translate(key))
def get(self, key: Any) -> Any:
return super().get(self._translate(key))
def _make_rich_color_theme(style_name: Optional[str] = None) -> 'Theme':
from rich.style import Style
from rich.theme import Theme
try:
PieStyle(style_name)
except ValueError:
style = Styles.ANSI
else:
style = Styles.PIE
theme = Theme()
for color, color_set in ChainMap(
GenericColor.__members__, CUSTOM_STYLES
).items():
if isinstance(color_set, _StyledGenericColor):
properties = dict.fromkeys(color_set.styles, True)
color_set = color_set.color
else:
properties = {}
theme.styles[color.lower()] = Style(
color=color_set.apply_style(style, style_name=style_name),
**properties,
)
# E.g translate GenericColor.BLUE into blue on key access
theme.styles = _GenericColorCaster(theme.styles)
return theme

View File

@ -6,7 +6,7 @@ from contextlib import contextmanager
from rich.console import Console, RenderableType
from rich.highlighter import Highlighter
from httpie.output.ui.palette import make_rich_theme_from_style
from httpie.output.ui.rich_palette import _make_rich_color_theme
def render_as_string(renderable: RenderableType) -> str:
@ -14,7 +14,7 @@ def render_as_string(renderable: RenderableType) -> str:
return a *style-less* version of it as a string."""
with open(os.devnull, 'w') as null_stream:
fake_console = Console(file=null_stream, record=True, theme=make_rich_theme_from_style())
fake_console = Console(file=null_stream, record=True, theme=_make_rich_color_theme())
fake_console.print(renderable)
return fake_console.export_text()

View File

@ -277,7 +277,7 @@ def open_with_lockfile(file: Path, *args, **kwargs) -> Generator[IO[Any], None,
target_file = Path(tempfile.gettempdir()) / file_id
# Have an atomic-like touch here, so we'll tighten the possibility of
# a race occuring between multiple processes accessing the same file.
# a race occurring between multiple processes accessing the same file.
try:
target_file.touch(exist_ok=False)
except FileExistsError as exc:

View File

@ -1,9 +1,12 @@
"""Miscellaneous regression tests"""
import pytest
from httpie.cli.argtypes import KeyValueArgType
from httpie.cli.constants import SEPARATOR_HEADER, SEPARATOR_QUERY_PARAM, SEPARATOR_DATA_STRING
from httpie.cli.requestitems import RequestItems
from httpie.compat import is_windows
from .utils.matching import assert_output_matches, Expect
from .utils import HTTP_OK, MockEnvironment, http
from .utils.matching import assert_output_matches, Expect
def test_Host_header_overwrite(httpbin):
@ -47,3 +50,21 @@ def test_verbose_redirected_stdout_separator(httpbin):
Expect.RESPONSE_HEADERS,
Expect.BODY,
])
@pytest.mark.parametrize(['separator', 'target'], [
(SEPARATOR_HEADER, 'headers'),
(SEPARATOR_QUERY_PARAM, 'params'),
(SEPARATOR_DATA_STRING, 'data'),
])
def test_initial_backslash_number(separator, target):
"""
<https://github.com/httpie/httpie/issues/1408>
"""
back_digit = r'\0'
raw_arg = back_digit + separator + back_digit
expected_parsed_data = {back_digit: back_digit}
parsed_arg = KeyValueArgType(separator)(raw_arg)
items = RequestItems.from_args([parsed_arg])
parsed_data = getattr(items, target)
assert parsed_data == expected_parsed_data