countrykit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- countrykit-0.1.0/PKG-INFO +72 -0
- countrykit-0.1.0/README.md +58 -0
- countrykit-0.1.0/pyproject.toml +22 -0
- countrykit-0.1.0/src/countrykit/__init__.py +0 -0
- countrykit-0.1.0/src/countrykit/api.py +49 -0
- countrykit-0.1.0/src/countrykit/cache.py +39 -0
- countrykit-0.1.0/src/countrykit/cli.py +41 -0
- countrykit-0.1.0/src/countrykit/models.py +27 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: countrykit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: FeliciaUmana
|
|
6
|
+
Author-email: feliciaumana1235@gmail.com
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Requires-Dist: requests (>=2.34.2,<3.0.0)
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
\# countrykit
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
A simple Python library and CLI tool for looking up facts about any country, comparing two countries, and caching results locally so repeat lookups don't hit the internet.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
\## Install
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
pip install countrykit
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
\## PyPI
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
https://pypi.org/project/countrykit/
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
\## Usage
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
\*\*From the terminal:\*\*
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
  countrykit info Nigeria
|
|
47
|
+
|
|
48
|
+
  countrykit compare Nigeria Ghana
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
\*\*From Python:\*\*
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
  from countrykit.api import fetch\_country
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
  nigeria = fetch\_country("Nigeria")
|
|
61
|
+
|
|
62
|
+
  print(nigeria.to\_dict())
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
\## About the caret (^) in dependencies
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
In `pyproject.toml`, a dependency like `requests = "^2.34.2"` uses Poetry's caret constraint. This means any version from `2.34.2` up to (but not including) `3.0.0` is accepted. The caret allows automatic updates that don't change the leftmost non-zero version digit, which follows semantic versioning: minor and patch updates are expected to be backward-compatible, while a major version bump signals breaking changes. This helps prevent dependency mismatches, since it lets Poetry's resolver pick a version that satisfies multiple packages' requirements, while still protecting your project from unexpected breaking changes.
|
|
71
|
+
|
|
72
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
\# countrykit
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
A simple Python library and CLI tool for looking up facts about any country, comparing two countries, and caching results locally so repeat lookups don't hit the internet.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
\## Install
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
pip install countrykit
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
\## PyPI
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
https://pypi.org/project/countrykit/
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
\## Usage
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
\*\*From the terminal:\*\*
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
  countrykit info Nigeria
|
|
34
|
+
|
|
35
|
+
  countrykit compare Nigeria Ghana
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
\*\*From Python:\*\*
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
  from countrykit.api import fetch\_country
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
  nigeria = fetch\_country("Nigeria")
|
|
48
|
+
|
|
49
|
+
  print(nigeria.to\_dict())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
\## About the caret (^) in dependencies
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
In `pyproject.toml`, a dependency like `requests = "^2.34.2"` uses Poetry's caret constraint. This means any version from `2.34.2` up to (but not including) `3.0.0` is accepted. The caret allows automatic updates that don't change the leftmost non-zero version digit, which follows semantic versioning: minor and patch updates are expected to be backward-compatible, while a major version bump signals breaking changes. This helps prevent dependency mismatches, since it lets Poetry's resolver pick a version that satisfies multiple packages' requirements, while still protecting your project from unexpected breaking changes.
|
|
58
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "countrykit"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "FeliciaUmana",email = "feliciaumana1235@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"requests (>=2.34.2,<3.0.0)"
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
countrykit = "countrykit.cli:main"
|
|
16
|
+
|
|
17
|
+
[tool.poetry]
|
|
18
|
+
packages = [{include = "countrykit", from = "src"}]
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
22
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import requests
|
|
3
|
+
from .models import Country
|
|
4
|
+
from .cache import Cache
|
|
5
|
+
|
|
6
|
+
API_KEY = os.environ.get("RESTCOUNTRIES_API_KEY")
|
|
7
|
+
|
|
8
|
+
def fetch_country(name: str):
|
|
9
|
+
cache = Cache()
|
|
10
|
+
|
|
11
|
+
# 1. Check cache first
|
|
12
|
+
cached = cache.get(name)
|
|
13
|
+
if cached is not None:
|
|
14
|
+
return cached
|
|
15
|
+
|
|
16
|
+
# 2. Not cached — call the API
|
|
17
|
+
url = f"https://api.restcountries.com/countries/v5/names.common/{name}"
|
|
18
|
+
headers = {"Authorization": f"Bearer {API_KEY}"}
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
response = requests.get(url, headers=headers, timeout=5)
|
|
22
|
+
except requests.exceptions.RequestException:
|
|
23
|
+
# API unreachable (network error, timeout, DNS failure, etc.)
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
if response.status_code != 200:
|
|
27
|
+
# Country not found, or some other API-side error
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
data = response.json()
|
|
31
|
+
objects = data["data"]["objects"]
|
|
32
|
+
|
|
33
|
+
if not objects:
|
|
34
|
+
# No matching country
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
obj = objects[0]
|
|
38
|
+
country = Country(
|
|
39
|
+
name=obj["names"]["common"],
|
|
40
|
+
capital=obj["capitals"][0]["name"] if obj["capitals"] else "",
|
|
41
|
+
population=obj["population"],
|
|
42
|
+
region=obj["region"],
|
|
43
|
+
currency=obj["currencies"][0]["name"] if obj["currencies"] else "",
|
|
44
|
+
languages=[lang["name"] for lang in obj["languages"]]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 3. Save to cache before returning
|
|
48
|
+
cache.save(country)
|
|
49
|
+
return country
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
|
|
3
|
+
from .models import Country
|
|
4
|
+
|
|
5
|
+
class Cache:
|
|
6
|
+
def __init__(self, db_path: str = "countrykit_cache.db"):
|
|
7
|
+
self.conn = sqlite3.connect(db_path)
|
|
8
|
+
self.cursor = self.conn.cursor()
|
|
9
|
+
self.cursor.execute("""
|
|
10
|
+
CREATE TABLE IF NOT EXISTS countries (
|
|
11
|
+
name TEXT PRIMARY KEY,
|
|
12
|
+
capital TEXT,
|
|
13
|
+
population INTEGER,
|
|
14
|
+
region TEXT,
|
|
15
|
+
currency TEXT,
|
|
16
|
+
languages TEXT
|
|
17
|
+
)
|
|
18
|
+
""")
|
|
19
|
+
self.conn.commit()
|
|
20
|
+
|
|
21
|
+
def save(self, country: "Country") -> None:
|
|
22
|
+
languages_str = ", ".join(country.languages)
|
|
23
|
+
self.cursor.execute(
|
|
24
|
+
"INSERT OR REPLACE INTO countries VALUES (?, ?, ?, ?, ?, ?)",
|
|
25
|
+
(country.name, country.capital, country.population, country.region, country.currency, languages_str))
|
|
26
|
+
self.conn.commit()
|
|
27
|
+
|
|
28
|
+
def get(self, name: str):
|
|
29
|
+
self.cursor.execute('SELECT * FROM countries WHERE name = ?', (name,))
|
|
30
|
+
row = self.cursor.fetchone()
|
|
31
|
+
if row is None:
|
|
32
|
+
return None
|
|
33
|
+
name, capital, population, region, currency, languages_str = row
|
|
34
|
+
languages = languages_str.split(',')
|
|
35
|
+
return Country (name, capital, population, region, currency, languages)
|
|
36
|
+
|
|
37
|
+
def clear(self):
|
|
38
|
+
self.cursor.execute('DELETE FROM countries')
|
|
39
|
+
self.conn.commit()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from .api import fetch_country
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
parser = argparse.ArgumentParser()
|
|
8
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
9
|
+
|
|
10
|
+
info_parser = subparsers.add_parser("info")
|
|
11
|
+
info_parser.add_argument("country")
|
|
12
|
+
|
|
13
|
+
compare_parser = subparsers.add_parser("compare")
|
|
14
|
+
compare_parser.add_argument("country1")
|
|
15
|
+
compare_parser.add_argument("country2")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
args = parser.parse_args()
|
|
19
|
+
|
|
20
|
+
if args.command == 'info':
|
|
21
|
+
country = fetch_country(args.country)
|
|
22
|
+
if country is None:
|
|
23
|
+
print(f"Country '{args.country}' not found.")
|
|
24
|
+
else:
|
|
25
|
+
print(country.to_dict())
|
|
26
|
+
|
|
27
|
+
elif args.command == 'compare':
|
|
28
|
+
country1 = fetch_country(args.country1)
|
|
29
|
+
country2 = fetch_country(args.country2)
|
|
30
|
+
if country1 is None or country2 is None:
|
|
31
|
+
print('one or both countries not found.')
|
|
32
|
+
else:
|
|
33
|
+
print(country1.compare(country2))
|
|
34
|
+
|
|
35
|
+
else:
|
|
36
|
+
parser.print_help()
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
|
40
|
+
|
|
41
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class Country:
|
|
2
|
+
def __init__(self, name: str, capital: str, population: int, region: str, currency: str, languages: list[str]):
|
|
3
|
+
self.name = name
|
|
4
|
+
self.capital = capital
|
|
5
|
+
self.population = population
|
|
6
|
+
self.region = region
|
|
7
|
+
self.currency = currency
|
|
8
|
+
self.languages = languages
|
|
9
|
+
|
|
10
|
+
def to_dict(self) -> dict:
|
|
11
|
+
return {
|
|
12
|
+
'name': self.name,
|
|
13
|
+
'capital': self.capital,
|
|
14
|
+
'population': self.population,
|
|
15
|
+
'region': self.region,
|
|
16
|
+
'currency': self.currency,
|
|
17
|
+
'languages': self.languages
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def compare(self, other: 'Country') -> str:
|
|
21
|
+
diff = abs(self.population - other.population)
|
|
22
|
+
if self.population > other.population:
|
|
23
|
+
return f"{self.name} has {diff:,} more people than {other.name}."
|
|
24
|
+
elif other.population > self.population:
|
|
25
|
+
return f"{other.name} has {diff:,} more people than {self.name}."
|
|
26
|
+
else:
|
|
27
|
+
return f"{self.name} and {other.name} have the same population."
|