Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 33 additions & 1 deletion cli/src/av_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@ def _python_type_to_click(param_type):



def _apply_limit(result, limit):
"""Slice the time-series portion of a response to `limit` entries."""
if limit is None:
return result
if isinstance(result, str):
lines = result.splitlines()
# Keep header row + limit data rows
header = lines[0:1]
data = lines[1:limit + 1]
return '\n'.join(header + data)
if not isinstance(result, dict):
return result
for key, value in result.items():
if isinstance(value, dict) and value:
first_val = next(iter(value.values()))
if isinstance(first_val, dict):
result = {**result, key: dict(list(value.items())[:limit])}
break
return result


def _make_tool_command(func, tool_name):
"""Build a Click command for a single tool function."""
from av_api.registry import extract_description
Expand All @@ -39,7 +60,7 @@ def _make_tool_command(func, tool_name):
hints = {}

# Build short option flags, skipping conflicts with -h (help) and -k (api-key)
used_shorts = {'h', 'k'}
used_shorts = {'h', 'k', 'n'}
short_map = {}
for pname in sig.parameters:
for ch in pname.lower():
Expand All @@ -52,6 +73,15 @@ def _make_tool_command(func, tool_name):
has_symbol = 'symbol' in sig.parameters and sig.parameters['symbol'].default is inspect.Parameter.empty

params = []
# Add --limit/-n to cap the number of returned data points
params.append(
click.Option(
['--limit', '-n'],
type=click.INT,
default=None,
help='Maximum number of data points to return.',
)
)
# Add --api-key/-k to each subcommand so it can appear after the command name
params.append(
click.Option(
Expand Down Expand Up @@ -102,6 +132,7 @@ def callback(**kwargs):
from av_api.context import set_api_key

local_api_key = kwargs.pop('api_key', None)
limit = kwargs.pop('limit', None)
ctx = click.get_current_context()
api_key = local_api_key or ctx.obj.get('api_key') or os.getenv('ALPHAVANTAGE_API_KEY') or os.getenv('ALPHA_VANTAGE_API_KEY')
if not api_key:
Expand All @@ -117,6 +148,7 @@ def callback(**kwargs):

set_api_key(api_key)
result = func(**kwargs)
result = _apply_limit(result, limit)

if isinstance(result, str):
click.echo(result)
Expand Down
48 changes: 48 additions & 0 deletions cli/tests/test_limit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from av_cli.main import _apply_limit


# 1. limit=None is a no-op
def test_apply_limit_none_returns_unchanged():
data = {"Time Series (Daily)": {"2026-03-18": {"1. open": "252.00"}}}
assert _apply_limit(data, None) is data


# 2. CSV string: header kept, data rows sliced to limit
def test_apply_limit_csv_slices_rows():
csv = "timestamp,open,high,low,close\n2026-03-18,252,255,249,250\n2026-03-17,251,253,248,249\n2026-03-16,250,252,247,248"
result = _apply_limit(csv, 2)
lines = result.splitlines()
assert lines[0] == "timestamp,open,high,low,close"
assert len(lines) == 3 # header + 2 data rows


# 3. JSON nested dict: inner time series sliced to limit
def test_apply_limit_nested_dict_slices_entries():
data = {
"Meta Data": {"1. Information": "Daily"},
"Time Series (Daily)": {
"2026-03-18": {"1. open": "252.00"},
"2026-03-17": {"1. open": "251.00"},
"2026-03-16": {"1. open": "250.00"},
},
}
result = _apply_limit(data, 2)
assert len(result["Time Series (Daily)"]) == 2


# 4. Non-dict, non-string passthrough (e.g. None or list)
def test_apply_limit_passthrough_for_unsupported_types():
assert _apply_limit(None, 5) is None
lst = [1, 2, 3]
assert _apply_limit(lst, 5) is lst


# 5. CLI integration: --limit flag is wired up to the command
def test_limit_flag_exists_on_commands():
from click.testing import CliRunner
from av_cli.main import cli

runner = CliRunner()
result = runner.invoke(cli, ["TIME_SERIES_DAILY", "--help"])
assert "--limit" in result.output
assert "-n" in result.output