diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82f9275 --- /dev/null +++ b/.gitignore @@ -0,0 +1,162 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/API_handling/__init__.py b/API_handling/__init__.py new file mode 100644 index 0000000..8c31ae2 --- /dev/null +++ b/API_handling/__init__.py @@ -0,0 +1,3 @@ +from .animals import fetch_animal_info, display_animal_info, animal +from .facts import fetch_fact, display_fact, fact +from .quotes import fetch_quote, display_quote, quote \ No newline at end of file diff --git a/API_handling/animals.py b/API_handling/animals.py new file mode 100644 index 0000000..4ee2834 --- /dev/null +++ b/API_handling/animals.py @@ -0,0 +1,54 @@ +import requests +from requests import ConnectionError +from pprint import pprint +from typing import List, Dict +from rich.console import Console +from rich import print_json +console = Console() + +def fetch_animal_info(name : str) -> List[Dict]: + api_url = 'https://api.api-ninjas.com/v1/animals?name={}'.format(name) + try: + response = requests.get(api_url, headers={'X-Api-Key': 'yCznsy0vc3u7k2r8U9RrLQ==LiWWdUaMMfVAiTb6'}) + if response.status_code == requests.codes.ok: + if response.json(): + return response.json() + else: + console.print(f"[red]No animals found matching '{name}'.") + return [] + else: + console.print(f"[red]Error: {response.status_code} {response.text}") + return [] + except ConnectionError: + console.print("[red]No internet connection! Please check your network and try again.[/red]") + return None + +def display_animal_info(animals_data: List[Dict]): + if not animals_data: + return + + for data_dict in animals_data: + console.print("[green]\n=== Animal Information ===") + + console.print(f"[red]\nName:[/red] {data_dict['name']}") + + console.print("[cyan]\nTaxonomy:") + for key, value in data_dict['taxonomy'].items(): + console.print(f" [cyan i]{key.title().replace("_"," ")}:[/cyan i] {value}") + + console.print("[yellow]\nLocations:", ", ".join(data_dict['locations'])) + + console.print("[blue]\nCharacteristics:") + for key, value in data_dict['characteristics'].items(): + formatted_key = key.replace('_', ' ').title() + console.print(f" [red i]{formatted_key}:[/red i] {value}") + +def animal(animal_name : str = None) -> None: + if not animal_name: + console.print("[yellow] Please provide an animal name.") + return + with console.status("Monkeying around...", spinner="monkey"): + display_animal_info(fetch_animal_info(name=animal_name)) + +if __name__ == "__main__": + animal("CHEETAH") \ No newline at end of file diff --git a/API_handling/facts.py b/API_handling/facts.py new file mode 100644 index 0000000..0ec3d50 --- /dev/null +++ b/API_handling/facts.py @@ -0,0 +1,35 @@ +import requests +from requests import ConnectionError +from typing import List, TypedDict, Optional +from rich.console import Console +console = Console() + +class Fact(TypedDict): + fact: str + +def fetch_fact() -> Optional[List[Fact]]: + api_url = 'https://api.api-ninjas.com/v1/facts' + try: + response = requests.get(api_url, headers={'X-Api-Key': 'yCznsy0vc3u7k2r8U9RrLQ==LiWWdUaMMfVAiTb6'}) + if response.status_code == requests.codes.ok: + fact = response.json() + if isinstance(fact, list) and len(fact) > 0: + return fact + console.print("[yellow]No facts received from the API!") + else: + console.print(f"[red]Error: {response.status_code} {response.text}") + except ConnectionError: + console.print("[red]No internet connection! Please check your network and try again.[/red]") + return None + +def display_fact(fact: Optional[List[Fact]]) -> None: + if not fact: + console.print("[yellow] No facts to display!") + return + console.print(f"[green bold]Fun Fact:[/green bold] {fact[0]['fact']}") + +def fact() -> None: + with console.status("Fetching a fun fact...", spinner="earth"): + display_fact(fetch_fact()) +if __name__ == "__main__": + fact() \ No newline at end of file diff --git a/API_handling/quotes.py b/API_handling/quotes.py new file mode 100644 index 0000000..0c5d31b --- /dev/null +++ b/API_handling/quotes.py @@ -0,0 +1,64 @@ +import requests +from requests import ConnectionError +import random +from typing import Union, List, TypedDict, Optional +from rich.console import Console +console = Console() + +class Quote(TypedDict): + quote : str + author : str + category : str + +categories = ["age", "alone", "amazing", "anger", "architecture", "art", "attitude", "beauty", "best", "birthday", "business", "car", "change", "communication", "computers", "cool", "courage", "dad", "dating", "death", "design", "dreams", "education", "environmental", "equality", "experience", "failure", "faith", "family", "famous", "fear", "fitness", "food", "forgiveness", "freedom", "friendship", "funny", "future", "god", "good", "government", "graduation", "great", "happiness", "health", "history", "home", "hope", "humor", "imagination", "inspirational", "intelligence", "jealousy", "knowledge", "leadership", "learning", "legal", "life", "love", "marriage", "medical", "men", "mom", "money", "morning", "movies", "success"] + +categories_noun = { + "alone": "loneliness", + "amazing": "amazement", + "best": "excellence", + "cool": "coolness", + "famous": "fame", + "funny": "humor", + "good": "goodness", + "great": "greatness", + "inspirational": "inspiration", + "legal": "legality", + "medical": "medicine", +} + +def fetch_quote(category : Optional[str] = None) -> Union[List[Quote], int]: + if not category: + category = random.choice(categories) + else: + category = {v: k for k, v in categories_noun.items()}.get(category.lower(), category.lower()) + if category not in categories: + console.print(f"[red]Invalid category '{category}'. Falling back to random choice![/red]") + category = random.choice(categories) + api_url = 'https://api.api-ninjas.com/v1/quotes?category={}'.format(category) + try: + response = requests.get(api_url, headers={'X-Api-Key': 'yCznsy0vc3u7k2r8U9RrLQ==LiWWdUaMMfVAiTb6'}) + if response.status_code == requests.codes.ok: + return response.json() + else: + console.print(f"[red]Error: {response.status_code} {response.text}") + return 0 + except ConnectionError: + console.print("[red]No internet connection! Please check your network and try again.[/red]") + return 0 + +def display_quote(quote : Union[List[Quote], int]) -> None: + if quote == 0: + return + quote = quote[0] + topic = categories_noun.get(quote['category'],quote['category']).capitalize() + console.print(f''' + [green bold]"{quote['quote']}"[/green bold] + said by [cyan i]{quote['author']}[/cyan i] about [red]{topic}[/red] + ''') + +def quote(category : Optional[str] = None) -> None: + with console.status("Fetching a quote...", spinner="earth"): + display_quote(quote=fetch_quote(category)) + +if __name__ == "__main__": + quote() \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..58ccbf8 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Fun APIs CLI + +A simple Python command-line interface utility to interact with various fun APIs: facts, animals, quotes. +This project, designed for learning and fun, can pull random facts, animal data, quotes and much more via API Ninjas. + +## Installation + +Clone the repository: +```bash +git clone https://github.com/tahadnan/Fun-APIs-CLI.git +cd Fun-APIs-CLI +``` + +(Highly Recommended)Set up a virtual environment and install dependencies: +```bash +python3 -m venv venv +source venv/bin/activate # For Mac/Linux +.\venv\Scripts\activate # For Windows +pip install -r requirements.txt +``` +Run the progrm: +```bash +python3 main.py -h +``` + diff --git a/main.py b/main.py new file mode 100644 index 0000000..8bb8054 --- /dev/null +++ b/main.py @@ -0,0 +1,31 @@ +import argparse +import sys +import requests +from requests import ConnectionError +from rich.console import Console +from API_handling import fact, animal, quote + +console = Console() + +parser = argparse.ArgumentParser( + prog='Fun APIs', + description='Playing with some Ninjas APIs', + epilog='Enjoy it!') + +parser.add_argument("--fact", help="Displays a random fact.", action='store_true') +parser.add_argument("--animal", help="Displays animal information.", nargs="+",type=str) +parser.add_argument("--quote", help="Displays a random quote.", type=str) + +args = parser.parse_args() + + +if args.fact: + fact() + +if args.animal: + animal(" ".join(args.animal)) + +if args.quote: + quote(args.quote) + +args = parser.parse_args() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5007430 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +certifi==2024.8.30 +charset-normalizer==3.4.0 +idna==3.10 +markdown-it-py==3.0.0 +mdurl==0.1.2 +Pygments==2.18.0 +requests==2.32.3 +rich==13.9.4 +soupsieve==2.6 +urllib3==2.2.3