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.
@@ -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
+ &#x20; countrykit info Nigeria
47
+
48
+ &#x20; countrykit compare Nigeria Ghana
49
+
50
+
51
+
52
+ \*\*From Python:\*\*
53
+
54
+
55
+
56
+ &#x20; from countrykit.api import fetch\_country
57
+
58
+
59
+
60
+ &#x20; nigeria = fetch\_country("Nigeria")
61
+
62
+ &#x20; 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
+ &#x20; countrykit info Nigeria
34
+
35
+ &#x20; countrykit compare Nigeria Ghana
36
+
37
+
38
+
39
+ \*\*From Python:\*\*
40
+
41
+
42
+
43
+ &#x20; from countrykit.api import fetch\_country
44
+
45
+
46
+
47
+ &#x20; nigeria = fetch\_country("Nigeria")
48
+
49
+ &#x20; 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."