diff --git a/.github/workflows/fred-py-api-package.yml b/.github/workflows/fred-py-api-package.yml index d621d88..cf0a67d 100644 --- a/.github/workflows/fred-py-api-package.yml +++ b/.github/workflows/fred-py-api-package.yml @@ -33,16 +33,14 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install requests coverage black==22.6.0 - python -m pip install -e . - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + python -m pip install -e .[ci] - name: Lint with black run: | # Run black on all Python files - black --line-length 120 --check ./ + black --check ./ - name: Test with coverage run: | - coverage run --source="src" --omit="src/fred/cli/__main__.py","src/fred/cli/__init__.py" -m unittest + coverage run -m unittest coverage report -m - name: Upload coverage report uses: codecov/codecov-action@v2 diff --git a/pyproject.toml b/pyproject.toml index 2e8c42b..21ba1ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta" [project] name = "fred-py-api" -version = "1.1.1" +version = "1.1.2" authors = [ - { name="Zachary Spar", email="zachspar@gmail.com" }, - { name="Prasiddha Parthsarthy", email="prasiddha@gmail.com" }, + { name="Zachary Spar", email="zachspar@gmail.com" }, + { name="Prasiddha Parthsarthy", email="prasiddha@gmail.com" }, ] description = "A fully featured FRED Command Line Interface & Python API client library." readme = "README.md" @@ -26,13 +26,44 @@ classifiers = [ "Operating System :: OS Independent", ] dependencies = [ - "requests>=2.17.3", - "click>=7.0", + "requests>=2.17.3", + "click>=7.0", +] + +[project.optional-dependencies] +ci = [ + "black==22.6.0", + "coverage==6.4.2", +] +dev = [ + "black==22.6.0", + "coverage==6.4.2", + "tox==3.25.1", ] [project.scripts] -fred = "fred.cli:__main__.run_cli" +fred = "fred.cli:__main__.run_fred_cli" +categories = "fred.cli:categories.run_categories_cli" +releases = "fred.cli:releases.run_releases_cli" +series = "fred.cli:series.run_series_cli" +sources = "fred.cli:sources.run_sources_cli" +tags = "fred.cli:tags.run_tags_cli" [project.urls] "Homepage" = "https://github.com/zachspar/fred_py_api" "Bug Tracker" = "https://github.com/zachspar/fred_py_api/issues" + +[tool.coverage.run] +branch = true +source = ["src"] + +[tool.coverage.report] +exclude_lines = [ + "if __name__ == .__main__.:", +] + +[tool.coverage.html] +directory = "coverage_html_report" + +[tool.black] +line-length = 120 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 2f475e2..0000000 --- a/requirements.txt +++ /dev/null @@ -1,25 +0,0 @@ -black==22.6.0 -build==0.8.0 -certifi==2023.7.22 -charset-normalizer==2.1.0 -click==8.1.3 -coverage==6.4.2 -distlib==0.3.5 -filelock==3.7.1 -idna==3.3 -mypy-extensions==0.4.3 -packaging==21.3 -pathspec==0.9.0 -pep517==0.12.0 -platformdirs==2.5.2 -pluggy==1.0.0 -py==1.11.0 -pyparsing==3.0.9 -requests==2.31.0 -six==1.16.0 -toml==0.10.2 -tomli==2.0.1 -tox==3.25.1 -typing_extensions==4.3.0 -urllib3==1.26.18 -virtualenv==20.15.1 diff --git a/src/fred/_util/cli.py b/src/fred/_util/cli.py index 667291f..cdbb3c9 100644 --- a/src/fred/_util/cli.py +++ b/src/fred/_util/cli.py @@ -3,13 +3,17 @@ FRED CLI Utilities. """ from json import dumps -from typing import Union +from typing import Union, Callable from xml.etree import ElementTree as ET +import click + __all__ = [ "generate_api_kwargs", "serialize", + "run_cli_callable", + "init_cli_context", ] @@ -32,3 +36,26 @@ def serialize(response_obj: Union[dict, ET.Element]) -> str: return ET.tostring(response_obj, encoding="unicode", method="xml") else: raise TypeError("response_obj must be a dict or xml.etree.ElementTree.Element") + + +def run_cli_callable(cli_callable: Callable) -> None: + """ + Run a CLI callable. + """ + try: + cli_callable(auto_envvar_prefix="FRED") + except AssertionError: + click.echo(click.style("Error: FRED_API_KEY is not set!", fg="red")) + + +def init_cli_context(ctx: click.Context, api_key: str, api_client_class: Callable) -> None: + """ + Initialize a CLI context. + """ + ctx.ensure_object(dict) + + if "api_key" not in ctx.obj: + ctx.obj["api_key"] = api_key + + if "client" not in ctx.obj: + ctx.obj["client"] = api_client_class(api_key=api_key) diff --git a/src/fred/cli/__init__.py b/src/fred/cli/__init__.py index 1cfabe9..c2e5891 100644 --- a/src/fred/cli/__init__.py +++ b/src/fred/cli/__init__.py @@ -4,11 +4,12 @@ from click import group from fred import FredAPI -from .categories import categories -from .releases import releases -from .series import series -from .sources import sources -from .tags import tags +from .categories import categories as categories_cli_callable +from .releases import releases as releases_cli_callable +from .series import series as series_cli_callable +from .sources import sources as sources_cli_callable +from .tags import tags as tags_cli_callable +from .._util.cli import init_cli_context __all__ = [ "fred_cli", @@ -18,16 +19,14 @@ @group() @click.option("--api-key", type=click.STRING, required=False, help="FRED API key.") @click.pass_context -def fred_cli(ctx, api_key: str): +def fred_cli(ctx: click.Context, api_key: str): """CLI for the Federal Reserve Economic Data (FRED).""" - ctx.ensure_object(dict) - ctx.obj["api_key"] = api_key - ctx.obj["client"] = FredAPI(api_key=api_key) + init_cli_context(ctx, api_key, FredAPI) # add each FRED command group -fred_cli.add_command(categories) -fred_cli.add_command(releases) -fred_cli.add_command(series) -fred_cli.add_command(sources) -fred_cli.add_command(tags) +fred_cli.add_command(categories_cli_callable) +fred_cli.add_command(releases_cli_callable) +fred_cli.add_command(series_cli_callable) +fred_cli.add_command(sources_cli_callable) +fred_cli.add_command(tags_cli_callable) diff --git a/src/fred/cli/__main__.py b/src/fred/cli/__main__.py index 22a9c28..590bad6 100644 --- a/src/fred/cli/__main__.py +++ b/src/fred/cli/__main__.py @@ -1,16 +1,17 @@ #!/usr/bin/env python3 -import click - from . import fred_cli +from .._util import run_cli_callable + + +__all__ = [ + "run_fred_cli", +] -def run_cli(): - """Run the FRED CLI.""" - try: - fred_cli(auto_envvar_prefix="FRED") - except AssertionError: - click.echo(click.style("Error: FRED_API_KEY is not set!", fg="red")) +def run_fred_cli(): + """Run the CLI.""" + run_cli_callable(cli_callable=fred_cli) if __name__ == "__main__": - run_cli() + run_fred_cli() diff --git a/src/fred/cli/categories.py b/src/fred/cli/categories.py index 2014a86..36cdbf9 100644 --- a/src/fred/cli/categories.py +++ b/src/fred/cli/categories.py @@ -4,28 +4,30 @@ """ import click -from .. import BaseFredAPIError -from .._util import generate_api_kwargs, serialize +from .. import BaseFredAPIError, FredAPICategories +from .._util import generate_api_kwargs, serialize, run_cli_callable, init_cli_context __all__ = [ "categories", + "run_categories_cli", ] @click.group() +@click.option("--api-key", type=click.STRING, required=False, help="FRED API key.") @click.pass_context -def categories(ctx): +def categories(ctx: click.Context, api_key: str): """ Categories CLI Namespace. """ - pass + init_cli_context(ctx, api_key, FredAPICategories) @categories.command() @click.option("--category-id", "-i", required=True, type=click.STRING, help="Category ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_category(ctx, category_id: int, args: tuple): +def get_category(ctx: click.Context, category_id: int, args: tuple): """Get a category.""" try: click.echo(serialize(ctx.obj["client"].get_category(category_id, **generate_api_kwargs(args)))) @@ -37,7 +39,7 @@ def get_category(ctx, category_id: int, args: tuple): @click.option("--category-id", "-i", required=True, type=click.STRING, help="Category ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_category_children(ctx, category_id: int, args: tuple): +def get_category_children(ctx: click.Context, category_id: int, args: tuple): """Get the child categories.""" try: click.echo(serialize(ctx.obj["client"].get_category_children(category_id, **generate_api_kwargs(args)))) @@ -49,7 +51,7 @@ def get_category_children(ctx, category_id: int, args: tuple): @click.option("--category-id", "-i", required=True, type=click.STRING, help="Category ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_category_related(ctx, category_id: int, args: tuple): +def get_category_related(ctx: click.Context, category_id: int, args: tuple): """Get related categories.""" try: click.echo(serialize(ctx.obj["client"].get_category_related(category_id, **generate_api_kwargs(args)))) @@ -61,7 +63,7 @@ def get_category_related(ctx, category_id: int, args: tuple): @click.option("--category-id", "-i", required=True, type=click.STRING, help="Category ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_category_series(ctx, category_id: int, args: tuple): +def get_category_series(ctx: click.Context, category_id: int, args: tuple): """Get series in a category.""" try: click.echo(serialize(ctx.obj["client"].get_category_series(category_id, **generate_api_kwargs(args)))) @@ -73,7 +75,7 @@ def get_category_series(ctx, category_id: int, args: tuple): @click.option("--category-id", "-i", required=True, type=click.STRING, help="Category ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_category_tags(ctx, category_id: int, args: tuple): +def get_category_tags(ctx: click.Context, category_id: int, args: tuple): """Get FRED tags for a category.""" try: click.echo(serialize(ctx.obj["client"].get_category_tags(category_id, **generate_api_kwargs(args)))) @@ -86,7 +88,7 @@ def get_category_tags(ctx, category_id: int, args: tuple): @click.option("--tag-names", "-t", required=True, type=click.STRING, help="Tag Names.") @click.argument("args", nargs=-1) @click.pass_context -def get_category_related_tags(ctx, category_id: int, tag_names: str, args: tuple): +def get_category_related_tags(ctx: click.Context, category_id: int, tag_names: str, args: tuple): """Get related FRED tags for a category.""" try: click.echo( @@ -94,3 +96,12 @@ def get_category_related_tags(ctx, category_id: int, tag_names: str, args: tuple ) except (ValueError, BaseFredAPIError) as e: raise click.UsageError(click.style(e, fg="red"), ctx) + + +def run_categories_cli(): + """Run the CLI for Categories namespace.""" + run_cli_callable(cli_callable=categories) + + +if __name__ == "__main__": + run_categories_cli() diff --git a/src/fred/cli/releases.py b/src/fred/cli/releases.py index 281d1f2..20f30bc 100644 --- a/src/fred/cli/releases.py +++ b/src/fred/cli/releases.py @@ -4,27 +4,26 @@ """ import click -from .. import BaseFredAPIError -from .._util import generate_api_kwargs, serialize +from .. import BaseFredAPIError, FredAPIReleases +from .._util import generate_api_kwargs, serialize, run_cli_callable, init_cli_context -__all__ = [ - "releases", -] +__all__ = ["releases", "run_releases_cli"] @click.group() +@click.option("--api-key", type=click.STRING, required=False, help="FRED API key.") @click.pass_context -def releases(ctx): +def releases(ctx: click.Context, api_key: str): """ Releases CLI Namespace. """ - pass + init_cli_context(ctx, api_key, FredAPIReleases) @releases.command() @click.argument("args", nargs=-1) @click.pass_context -def get_releases(ctx, args: tuple): +def get_releases(ctx: click.Context, args: tuple): """Get all releases of economic data.""" try: click.echo(serialize(ctx.obj["client"].get_releases(**generate_api_kwargs(args)))) @@ -35,7 +34,7 @@ def get_releases(ctx, args: tuple): @releases.command() @click.argument("args", nargs=-1) @click.pass_context -def get_releases_dates(ctx, args: tuple): +def get_releases_dates(ctx: click.Context, args: tuple): """Get release dates for all releases of economic data.""" try: click.echo(serialize(ctx.obj["client"].get_releases_dates(**generate_api_kwargs(args)))) @@ -47,7 +46,7 @@ def get_releases_dates(ctx, args: tuple): @click.option("--release-id", "-i", required=True, type=click.INT, help="Release ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_release(ctx, release_id: int, args: tuple): +def get_release(ctx: click.Context, release_id: int, args: tuple): """Get a release of economic data.""" try: click.echo(serialize(ctx.obj["client"].get_release(release_id, **generate_api_kwargs(args)))) @@ -59,7 +58,7 @@ def get_release(ctx, release_id: int, args: tuple): @click.option("--release-id", "-i", required=True, type=click.INT, help="Release ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_release_dates(ctx, release_id: int, args: tuple): +def get_release_dates(ctx: click.Context, release_id: int, args: tuple): """Get release dates for a release of economic data.""" try: click.echo(serialize(ctx.obj["client"].get_release_dates(release_id, **generate_api_kwargs(args)))) @@ -71,7 +70,7 @@ def get_release_dates(ctx, release_id: int, args: tuple): @click.option("--release-id", "-i", required=True, type=click.INT, help="Release ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_release_series(ctx, release_id: int, args: tuple): +def get_release_series(ctx: click.Context, release_id: int, args: tuple): """Get the series on a release of economic data.""" try: click.echo(serialize(ctx.obj["client"].get_release_series(release_id, **generate_api_kwargs(args)))) @@ -83,7 +82,7 @@ def get_release_series(ctx, release_id: int, args: tuple): @click.option("--release-id", "-i", required=True, type=click.INT, help="Release ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_release_sources(ctx, release_id: int, args: tuple): +def get_release_sources(ctx: click.Context, release_id: int, args: tuple): """Get the sources for a release of economic data.""" try: click.echo(serialize(ctx.obj["client"].get_release_sources(release_id, **generate_api_kwargs(args)))) @@ -95,7 +94,7 @@ def get_release_sources(ctx, release_id: int, args: tuple): @click.option("--release-id", "-i", required=True, type=click.INT, help="Release ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_release_tags(ctx, release_id: int, args: tuple): +def get_release_tags(ctx: click.Context, release_id: int, args: tuple): """Get the tags for a release.""" try: click.echo(serialize(ctx.obj["client"].get_release_tags(release_id, **generate_api_kwargs(args)))) @@ -108,7 +107,7 @@ def get_release_tags(ctx, release_id: int, args: tuple): @click.option("--tag-names", "-t", required=True, type=click.STRING, help="Tag Names.") @click.argument("args", nargs=-1) @click.pass_context -def get_release_related_tags(ctx, release_id: int, tag_names: str, args: tuple): +def get_release_related_tags(ctx: click.Context, release_id: int, tag_names: str, args: tuple): """Get the related tags for a release.""" try: click.echo( @@ -123,9 +122,20 @@ def get_release_related_tags(ctx, release_id: int, tag_names: str, args: tuple): @click.option("--element-id", "-e", required=False, type=click.INT, help="Element ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_release_tables(ctx, release_id: int, element_id: int, args: tuple): +def get_release_tables(ctx: click.Context, release_id: int, element_id: int, args: tuple): """Get the release table for a given release.""" try: click.echo(serialize(ctx.obj["client"].get_release_tables(release_id, element_id, **generate_api_kwargs(args)))) except (ValueError, BaseFredAPIError) as e: raise click.UsageError(click.style(e, fg="red"), ctx) + + +def run_releases_cli(): + """ + Run the CLI for the Releases namespace. + """ + run_cli_callable(cli_callable=releases) + + +if __name__ == "__main__": + run_releases_cli() diff --git a/src/fred/cli/series.py b/src/fred/cli/series.py index bbe0d35..e4c7148 100644 --- a/src/fred/cli/series.py +++ b/src/fred/cli/series.py @@ -4,28 +4,30 @@ """ import click -from .. import BaseFredAPIError -from .._util import generate_api_kwargs, serialize +from .. import BaseFredAPIError, FredAPISeries +from .._util import generate_api_kwargs, serialize, run_cli_callable, init_cli_context __all__ = [ "series", + "run_series_cli", ] @click.group() +@click.option("--api-key", type=click.STRING, required=False, help="FRED API key.") @click.pass_context -def series(ctx): +def series(ctx: click.Context, api_key: str): """ Series CLI Namespace. """ - pass + init_cli_context(ctx, api_key, FredAPISeries) @series.command() @click.option("--series-id", "-i", required=True, type=click.STRING, help="Series ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_series(ctx, series_id: str, args: tuple): +def get_series(ctx: click.Context, series_id: str, args: tuple): """Get series.""" try: click.echo(serialize(ctx.obj["client"].get_series(series_id, **generate_api_kwargs(args)))) @@ -37,7 +39,7 @@ def get_series(ctx, series_id: str, args: tuple): @click.option("--series-id", "-i", required=True, type=click.STRING, help="Series ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_series_categories(ctx, series_id: str, args: tuple): +def get_series_categories(ctx: click.Context, series_id: str, args: tuple): """Get series categories.""" try: click.echo(serialize(ctx.obj["client"].get_series_categories(series_id, **generate_api_kwargs(args)))) @@ -49,7 +51,7 @@ def get_series_categories(ctx, series_id: str, args: tuple): @click.option("--series-id", "-i", required=True, type=click.STRING, help="Series ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_series_observations(ctx, series_id: str, args: tuple): +def get_series_observations(ctx: click.Context, series_id: str, args: tuple): """Get series observations.""" try: click.echo(serialize(ctx.obj["client"].get_series_observations(series_id, **generate_api_kwargs(args)))) @@ -61,7 +63,7 @@ def get_series_observations(ctx, series_id: str, args: tuple): @click.option("--series-id", "-i", required=True, type=click.STRING, help="Series ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_series_release(ctx, series_id: str, args: tuple): +def get_series_release(ctx: click.Context, series_id: str, args: tuple): """Get series release.""" try: click.echo(serialize(ctx.obj["client"].get_series_release(series_id, **generate_api_kwargs(args)))) @@ -74,7 +76,7 @@ def get_series_release(ctx, series_id: str, args: tuple): @click.option("--search-type", "-s", required=False, type=click.STRING, help="Search type.", default="full_text") @click.argument("args", nargs=-1) @click.pass_context -def get_series_search(ctx, search_text: str, search_type: str, args: tuple): +def get_series_search(ctx: click.Context, search_text: str, search_type: str, args: tuple): """Get series search.""" try: click.echo( @@ -88,7 +90,7 @@ def get_series_search(ctx, search_text: str, search_type: str, args: tuple): @click.option("--series-search-text", "-t", required=True, type=click.STRING, help="Series search text.") @click.argument("args", nargs=-1) @click.pass_context -def get_series_search_tags(ctx, series_search_text: str, args: tuple): +def get_series_search_tags(ctx: click.Context, series_search_text: str, args: tuple): """Get series search tags.""" try: click.echo(serialize(ctx.obj["client"].get_series_search_tags(series_search_text, **generate_api_kwargs(args)))) @@ -101,7 +103,7 @@ def get_series_search_tags(ctx, series_search_text: str, args: tuple): @click.option("--tag-names", "-n", required=True, type=click.STRING, help="Tag names.") @click.argument("args", nargs=-1) @click.pass_context -def get_series_search_related_tags(ctx, series_search_text: str, tag_names: str, args: tuple): +def get_series_search_related_tags(ctx: click.Context, series_search_text: str, tag_names: str, args: tuple): """Get series search related tags.""" try: click.echo( @@ -119,7 +121,7 @@ def get_series_search_related_tags(ctx, series_search_text: str, tag_names: str, @click.option("--series-id", "-i", required=True, type=click.STRING, help="Series ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_series_tags(ctx, series_id: str, args: tuple): +def get_series_tags(ctx: click.Context, series_id: str, args: tuple): """Get series tags.""" try: click.echo(serialize(ctx.obj["client"].get_series_tags(series_id, **generate_api_kwargs(args)))) @@ -130,7 +132,7 @@ def get_series_tags(ctx, series_id: str, args: tuple): @series.command() @click.argument("args", nargs=-1) @click.pass_context -def get_series_updates(ctx, args: tuple): +def get_series_updates(ctx: click.Context, args: tuple): """Get series updates.""" try: click.echo(serialize(ctx.obj["client"].get_series_updates(**generate_api_kwargs(args)))) @@ -142,9 +144,20 @@ def get_series_updates(ctx, args: tuple): @click.option("--series-id", "-i", required=True, type=click.STRING, help="Series ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_series_vintagedates(ctx, series_id: str, args: tuple): +def get_series_vintagedates(ctx: click.Context, series_id: str, args: tuple): """Get series vintage dates.""" try: click.echo(serialize(ctx.obj["client"].get_series_vintagedates(series_id, **generate_api_kwargs(args)))) except (ValueError, BaseFredAPIError) as e: raise click.UsageError(click.style(e, fg="red"), ctx) + + +def run_series_cli(): + """ + Run the CLI for the Series Namespace. + """ + run_cli_callable(cli_callable=series) + + +if __name__ == "__main__": + run_series_cli() diff --git a/src/fred/cli/sources.py b/src/fred/cli/sources.py index c19e41e..6760559 100644 --- a/src/fred/cli/sources.py +++ b/src/fred/cli/sources.py @@ -4,26 +4,29 @@ """ import click -from .._util import generate_api_kwargs, serialize -from ..api import BaseFredAPIError +from ..api import BaseFredAPIError, FredAPISources +from .._util import generate_api_kwargs, serialize, run_cli_callable, init_cli_context __all__ = [ "sources", + "run_sources_cli", ] @click.group() -def sources(): +@click.option("--api-key", type=click.STRING, required=False, help="FRED API key.") +@click.pass_context +def sources(ctx: click.Context, api_key: str): """ Sources CLI Namespace. """ - pass + init_cli_context(ctx, api_key, FredAPISources) @sources.command() @click.argument("args", nargs=-1) @click.pass_context -def get_sources(ctx, args: tuple): +def get_sources(ctx: click.Context, args: tuple): """ Get sources. """ @@ -37,7 +40,7 @@ def get_sources(ctx, args: tuple): @click.option("--source-id", "-i", type=click.INT, required=True, help="Source ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_source(ctx, source_id: int, args: tuple): +def get_source(ctx: click.Context, source_id: int, args: tuple): """ Get source by ID. """ @@ -51,7 +54,7 @@ def get_source(ctx, source_id: int, args: tuple): @click.option("--source-id", "-i", type=click.INT, required=True, help="Source ID.") @click.argument("args", nargs=-1) @click.pass_context -def get_source_releases(ctx, source_id: int, args: tuple): +def get_source_releases(ctx: click.Context, source_id: int, args: tuple): """ Get source releases by ID. """ @@ -59,3 +62,14 @@ def get_source_releases(ctx, source_id: int, args: tuple): click.echo(serialize(ctx.obj["client"].get_source_releases(source_id, **generate_api_kwargs(args)))) except (ValueError, BaseFredAPIError) as e: raise click.UsageError(click.style(e, fg="red"), ctx) + + +def run_sources_cli(): + """ + Run the CLI for the Sources namespace. + """ + run_cli_callable(cli_callable=sources) + + +if __name__ == "__main__": + run_sources_cli() diff --git a/src/fred/cli/tags.py b/src/fred/cli/tags.py index 3738a1c..faf70a9 100644 --- a/src/fred/cli/tags.py +++ b/src/fred/cli/tags.py @@ -4,27 +4,29 @@ """ import click -from .. import BaseFredAPIError -from .._util import generate_api_kwargs, serialize +from .. import BaseFredAPIError, FredAPITags +from .._util import generate_api_kwargs, serialize, run_cli_callable, init_cli_context __all__ = [ "tags", + "run_tags_cli", ] @click.group() +@click.option("--api-key", type=click.STRING, required=False, help="FRED API key.") @click.pass_context -def tags(ctx): +def tags(ctx: click.Context, api_key: str): """ Tags CLI Namespace. """ - pass + init_cli_context(ctx, api_key, FredAPITags) @tags.command() @click.argument("args", nargs=-1) @click.pass_context -def get_tags(ctx, args: tuple): +def get_tags(ctx: click.Context, args: tuple): """ Get tags. """ @@ -38,7 +40,7 @@ def get_tags(ctx, args: tuple): @click.option("--tag-names", "-t", required=True, type=click.STRING, help="Tag Names.") @click.argument("args", nargs=-1) @click.pass_context -def get_related_tags(ctx, tag_names: str, args: tuple): +def get_related_tags(ctx: click.Context, tag_names: str, args: tuple): """ Get related tags. """ @@ -52,7 +54,7 @@ def get_related_tags(ctx, tag_names: str, args: tuple): @click.option("--tag-names", "-t", required=True, type=click.STRING, help="Tag Names.") @click.argument("args", nargs=-1) @click.pass_context -def get_tags_series(ctx, tag_names: str, args: tuple): +def get_tags_series(ctx: click.Context, tag_names: str, args: tuple): """ Get tag series. """ @@ -60,3 +62,12 @@ def get_tags_series(ctx, tag_names: str, args: tuple): click.echo(serialize(ctx.obj["client"].get_tags_series(tag_names, **generate_api_kwargs(args)))) except (ValueError, BaseFredAPIError) as e: raise click.UsageError(click.style(e, fg="red"), ctx) + + +def run_tags_cli(): + """Run the CLI for Tags namespace.""" + run_cli_callable(cli_callable=tags) + + +if __name__ == "__main__": + run_tags_cli() diff --git a/tests/cli/test_cli_entry_point.py b/tests/cli/test_cli_entry_point.py index 2146022..657d2eb 100644 --- a/tests/cli/test_cli_entry_point.py +++ b/tests/cli/test_cli_entry_point.py @@ -5,12 +5,117 @@ class TestCLIEntryPoint(BaseCLITest): """Test CLI entry point.""" - def test_cli_entry_point_import_and_run(self): + def test_fred_cli_entry_point_import_and_run(self): """Test the CLI entry point imports properly.""" - from fred.cli.__main__ import run_cli + from fred.cli.__main__ import run_fred_cli - self.assertIsNotNone(run_cli) + self.assertIsNotNone(run_fred_cli) # make sure CLI exits with sys.exit() with self.assertRaises(SystemExit): - run_cli() + run_fred_cli() + + def test_fred_cli_entrypoint_no_api_key(self): + """Test the CLI entry point from __init__.""" + from fred.cli import fred_cli + + self.assertIsNotNone(fred_cli) + + result = self.runner.invoke(fred_cli, ["--api-key", "fake_api_key", "tags"]) + self.assertEqual(result.exit_code, 1) + self.assertTrue(isinstance(result.exception, ValueError)) + + def test_categories_cli_entrypoint(self): + """Test the CLI entry point from categories.""" + from fred.cli.categories import categories + + self.assertIsNotNone(categories) + + result = self.runner.invoke(categories, ["--help"]) + self.assertEqual(result.exit_code, 0) + + def test_releases_cli_entrypoint(self): + """Test the CLI entry point from releases.""" + from fred.cli.releases import releases + + self.assertIsNotNone(releases) + + result = self.runner.invoke(releases, ["--help"]) + self.assertEqual(result.exit_code, 0) + + def test_series_cli_entrypoint(self): + """Test the CLI entry point from series.""" + from fred.cli.series import series + + self.assertIsNotNone(series) + + result = self.runner.invoke(series, ["--help"]) + self.assertEqual(result.exit_code, 0) + + def test_sources_cli_entrypoint(self): + """Test the CLI entry point from sources.""" + from fred.cli.sources import sources + + self.assertIsNotNone(sources) + + result = self.runner.invoke(sources, ["--help"]) + self.assertEqual(result.exit_code, 0) + + def test_tags_cli_entrypoint(self): + """Test the CLI entry point from tags.""" + from fred.cli.tags import tags + + self.assertIsNotNone(tags) + + result = self.runner.invoke(tags, ["--help"]) + self.assertEqual(result.exit_code, 0) + + def test_categories_cli_entry_point_import_and_run(self): + """Test the CLI entry point imports properly.""" + from fred.cli.categories import run_categories_cli + + self.assertIsNotNone(run_categories_cli) + + # make sure CLI exits with sys.exit() + with self.assertRaises(SystemExit): + run_categories_cli() + + def test_releases_cli_entry_point_import_and_run(self): + """Test the CLI entry point imports properly.""" + from fred.cli.releases import run_releases_cli + + self.assertIsNotNone(run_releases_cli) + + # make sure CLI exits with sys.exit() + with self.assertRaises(SystemExit): + run_releases_cli() + + def test_series_cli_entry_point_import_and_run(self): + """Test the CLI entry point imports properly.""" + from fred.cli.series import run_series_cli + + self.assertIsNotNone(run_series_cli) + + # make sure CLI exits with sys.exit() + with self.assertRaises(SystemExit): + run_series_cli() + + def test_sources_cli_entry_point_import_and_run(self): + """Test the CLI entry point imports properly.""" + from fred.cli.sources import run_sources_cli + + self.assertIsNotNone(run_sources_cli) + + # make sure CLI exits with sys.exit() + with self.assertRaises(SystemExit): + run_sources_cli() + + def test_tags_cli_entry_point_import_and_run(self): + """Test the CLI entry point imports properly.""" + from fred.cli.tags import run_tags_cli + + self.assertIsNotNone(run_tags_cli) + + # make sure CLI exits with sys.exit() + with self.assertRaises(SystemExit): + run_tags_cli() diff --git a/tests/cli/test_cli_utils.py b/tests/cli/test_cli_utils.py index 27a34da..a7fa6fb 100644 --- a/tests/cli/test_cli_utils.py +++ b/tests/cli/test_cli_utils.py @@ -1,8 +1,11 @@ #!/usr/bin/env python3 import unittest +from typing import Any from xml.etree import ElementTree as ET -from fred._util import generate_api_kwargs, serialize +import click + +from fred._util import generate_api_kwargs, serialize, run_cli_callable, init_cli_context class TestCLIUtils(unittest.TestCase): @@ -34,3 +37,35 @@ def test_serialize_error(self): response_obj = "invalid" with self.assertRaises(TypeError): serialize(response_obj) + + def test_run_cli_callable(self): + """Test the run_cli_callable function.""" + + def fake_cli_callable(auto_envvar_prefix: Any = None): + raise AssertionError("Fake Fred API callable.") + + self.assertIsNone(run_cli_callable(fake_cli_callable)) + + def test_init_cli_context(self): + """Test the init_cli_context function.""" + + @click.command() + @click.pass_context + def fake_cli_command(ctx: click.Context): + pass + + class FakeAPIClient: + def __init__(self, api_key: str): + pass + + ctx = click.Context(command=fake_cli_command) + api_key = "fake_api_key" + api_client_class = FakeAPIClient + + init_cli_context(ctx, api_key, api_client_class) + + # check that api_key is identical + self.assertEqual(ctx.obj["api_key"], api_key) + + # check that context client is of type FakeAPIClient + self.assertTrue(isinstance(ctx.obj["client"], api_client_class))