Skip to content

Commit d73470c

Browse files
committed
geomelon python client v1.0.0
0 parents  commit d73470c

12 files changed

Lines changed: 1152 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
runs-on: ${{ matrix.os }}
11+
strategy:
12+
fail-fast: false
13+
matrix:
14+
os: [ubuntu-latest]
15+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
16+
include:
17+
# 3.8/3.9 binaries are only available on older runner images
18+
- os: ubuntu-22.04
19+
python-version: '3.8'
20+
- os: ubuntu-22.04
21+
python-version: '3.9'
22+
steps:
23+
- uses: actions/checkout@v4
24+
- uses: actions/setup-python@v5
25+
with:
26+
python-version: ${{ matrix.python-version }}
27+
- name: Run tests
28+
run: python -m unittest discover -s tests -v
29+
30+
build:
31+
runs-on: ubuntu-latest
32+
needs: test
33+
steps:
34+
- uses: actions/checkout@v4
35+
- uses: actions/setup-python@v5
36+
with:
37+
python-version: '3.12'
38+
- name: Build sdist and wheel
39+
run: |
40+
python -m pip install --upgrade build twine
41+
python -m build
42+
twine check dist/*

.github/workflows/publish.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
build:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
- uses: actions/setup-python@v5
13+
with:
14+
python-version: '3.12'
15+
- name: Build sdist and wheel
16+
run: |
17+
python -m pip install --upgrade build
18+
python -m build
19+
- uses: actions/upload-artifact@v4
20+
with:
21+
name: dist
22+
path: dist/
23+
24+
publish:
25+
runs-on: ubuntu-latest
26+
needs: build
27+
environment: pypi
28+
permissions:
29+
id-token: write # required for PyPI trusted publishing
30+
steps:
31+
- uses: actions/download-artifact@v4
32+
with:
33+
name: dist
34+
path: dist/
35+
- uses: pypa/gh-action-pypi-publish@release/v1

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
__pycache__/
2+
*.py[cod]
3+
*.egg-info/
4+
dist/
5+
build/
6+
.venv/
7+
venv/
8+
.mypy_cache/
9+
.pytest_cache/
10+
.DS_Store

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Geomelon
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# geomelon
2+
3+
Python client for the [Geomelon API](https://geomelon.dev) — cities, countries, regions, and languages with multilingual support (50+ languages).
4+
5+
- **Zero dependencies** — standard library only (`urllib`)
6+
- **Python 3.8+** — tested on 3.8 through 3.14
7+
- **Fully typed** — dataclass models, `py.typed` marker, works with mypy/pyright
8+
- Sync API, one sub-client per resource, mirroring the [TypeScript client](https://github.com/930m310n/typescript)
9+
10+
## Install
11+
12+
```bash
13+
pip install geomelon
14+
```
15+
16+
## Try it — no API key needed
17+
18+
City autocomplete via the free oneshot endpoint, zero signup:
19+
20+
```python
21+
from geomelon import GeomelonClient
22+
23+
client = GeomelonClient() # no key
24+
for city in client.oneshot.search("es", "es", "barc"):
25+
print(city.name, city.population, city.emoji)
26+
# Barcelona 1620343 🇪🇸
27+
```
28+
29+
`search(iso, lang, prefix)` takes an ISO 3166-1 alpha-2 country code, a BCP 47 language code, and the city-name prefix as the user typed it — the server normalizes case, punctuation, and diacritics (`"Sant Cugat"`, `"barç"`, and `"barc"` all work). Everything else (full-text search, filters, coordinates, countries, regions, translations) requires an API key.
30+
31+
## Quickstart
32+
33+
Get an API key from [RapidAPI](https://rapidapi.com/hom3chuk/api/geomelon).
34+
35+
```python
36+
from geomelon import GeomelonClient
37+
38+
client = GeomelonClient(api_key="YOUR_RAPIDAPI_KEY")
39+
40+
# Prefix search with multilingual names
41+
cities = client.cities.search(name="barc", country_code="ES", preferred_languages="es,en")
42+
for city in cities:
43+
print(city.name, city.population, city.country_emoji)
44+
# Barcelona 1620343 🇪🇸
45+
46+
# Single city by UUID
47+
barcelona = client.cities.get("964512d1-f150-4876-87ec-0ba47aef694a")
48+
print(barcelona.time_zone) # Europe/Madrid
49+
print(barcelona.translations[0]) # CityTranslation(language='es', name='Barcelona')
50+
51+
# Nearest cities to coordinates
52+
nearby = client.cities.by_coordinates_closest(41.3828, 2.1769, preferred_languages="es")
53+
54+
# Countries, regions, languages
55+
spain = client.countries.get("a1e06cc1-817c-429f-84f4-6ab51dac9bfa")
56+
print(spain.iso_code, len(spain.regions))
57+
58+
# Distance between two cities (km)
59+
dist = client.cities.distance(city1="<uuid-1>", city2="<uuid-2>")
60+
print(dist.distance_km)
61+
62+
# Oneshot: pre-built static prefix search (fastest path)
63+
results = client.oneshot.search("es", "es", "ba")
64+
```
65+
66+
With an API key, oneshot requests route through the RapidAPI gateway and count toward your plan. Pass `free_oneshot=True` to send them to the free keyless host instead:
67+
68+
```python
69+
client = GeomelonClient(api_key="YOUR_RAPIDAPI_KEY", free_oneshot=True)
70+
```
71+
72+
## API surface
73+
74+
| Sub-client | Methods |
75+
|---|---|
76+
| `client.cities` | `search`, `get`, `translations`, `settlement_types`, `distance`, `by_coordinates_closest`, `by_coordinates_largest` |
77+
| `client.countries` | `list`, `get`, `translations`, `regions` |
78+
| `client.regions` | `list`, `get`, `translations` |
79+
| `client.languages` | `list`, `get` |
80+
| `client.oneshot` | `search` |
81+
82+
All method parameters are keyword arguments in `snake_case`; they map 1:1 to the API's camelCase query parameters (`country_code``countryCode`). `None` parameters are omitted from the request. Response models are dataclasses with `snake_case` fields; unknown response fields are ignored, so API additions never break older client versions.
83+
84+
## Error handling
85+
86+
All failures raise `GeomelonError`:
87+
88+
```python
89+
from geomelon import GeomelonClient, GeomelonError
90+
91+
client = GeomelonClient(api_key="YOUR_RAPIDAPI_KEY")
92+
try:
93+
client.cities.search(name="barc")
94+
except GeomelonError as err:
95+
if err.is_rate_limited: # HTTP 429
96+
...
97+
print(err.status, err.body, err.url)
98+
```
99+
100+
`status` is `None` for network-level failures (DNS, timeout, connection refused).
101+
102+
## Configuration
103+
104+
```python
105+
GeomelonClient(
106+
api_key="...", # optional; omit for free-oneshot-only mode
107+
host="geomelon.p.rapidapi.com", # override for staging/custom gateways
108+
timeout=30.0, # seconds, per request
109+
free_oneshot=False, # route oneshot to the keyless host even with a key
110+
)
111+
```
112+
113+
Calling a keyed endpoint on a keyless client raises `GeomelonError` immediately (no network request) with a message pointing to the RapidAPI signup.
114+
115+
## Development
116+
117+
```bash
118+
python -m unittest discover -s tests -v # run tests (no dev dependencies needed)
119+
python -m build # build sdist + wheel (pip install build)
120+
```
121+
122+
Publishing runs through GitHub Actions with [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) on GitHub release — see `.github/workflows/publish.yml`.
123+
124+
## License
125+
126+
MIT

pyproject.toml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "geomelon"
7+
dynamic = ["version"]
8+
description = "Python client for the Geomelon API — cities, countries, regions, and languages with multilingual support"
9+
readme = "README.md"
10+
license = "MIT"
11+
requires-python = ">=3.8"
12+
authors = [{ name = "Geomelon" }]
13+
keywords = ["geomelon", "cities", "countries", "geocoding", "geography", "api"]
14+
classifiers = [
15+
"Development Status :: 5 - Production/Stable",
16+
"Intended Audience :: Developers",
17+
"Operating System :: OS Independent",
18+
"Programming Language :: Python :: 3",
19+
"Programming Language :: Python :: 3.8",
20+
"Programming Language :: Python :: 3.9",
21+
"Programming Language :: Python :: 3.10",
22+
"Programming Language :: Python :: 3.11",
23+
"Programming Language :: Python :: 3.12",
24+
"Programming Language :: Python :: 3.13",
25+
"Programming Language :: Python :: 3.14",
26+
"Topic :: Software Development :: Libraries :: Python Modules",
27+
"Typing :: Typed",
28+
]
29+
30+
[project.urls]
31+
Homepage = "https://geomelon.dev"
32+
Repository = "https://github.com/930m310n/python"
33+
Issues = "https://github.com/930m310n/python/issues"
34+
35+
[tool.hatch.version]
36+
path = "src/geomelon/_version.py"
37+
38+
[tool.hatch.build.targets.wheel]
39+
packages = ["src/geomelon"]

src/geomelon/__init__.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Python client for the Geomelon API — cities, countries, regions, and
2+
languages with multilingual support. https://geomelon.dev"""
3+
4+
from ._version import __version__
5+
from .client import (
6+
DEFAULT_HOST,
7+
ONESHOT_FREE_HOST,
8+
CitiesClient,
9+
CountriesClient,
10+
GeomelonClient,
11+
GeomelonError,
12+
LanguagesClient,
13+
OneshotClient,
14+
RegionsClient,
15+
)
16+
from .types import (
17+
City,
18+
CityTranslation,
19+
CityTranslationRecord,
20+
Country,
21+
CountryExtended,
22+
CountryTranslationRecord,
23+
Distance,
24+
Language,
25+
OneshotCity,
26+
Region,
27+
RegionExtended,
28+
RegionTranslationRecord,
29+
SettlementType,
30+
)
31+
32+
__all__ = [
33+
"DEFAULT_HOST",
34+
"ONESHOT_FREE_HOST",
35+
"GeomelonClient",
36+
"GeomelonError",
37+
"CitiesClient",
38+
"CountriesClient",
39+
"RegionsClient",
40+
"LanguagesClient",
41+
"OneshotClient",
42+
"City",
43+
"CityTranslation",
44+
"CityTranslationRecord",
45+
"Country",
46+
"CountryExtended",
47+
"CountryTranslationRecord",
48+
"Distance",
49+
"Language",
50+
"OneshotCity",
51+
"Region",
52+
"RegionExtended",
53+
"RegionTranslationRecord",
54+
"SettlementType",
55+
"__version__",
56+
]

src/geomelon/_version.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "1.0.0"

0 commit comments

Comments
 (0)