namegender-client 0.3.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.
- namegender_client-0.3.0/LICENSE +1 -0
- namegender_client-0.3.0/PKG-INFO +77 -0
- namegender_client-0.3.0/README.md +50 -0
- namegender_client-0.3.0/pyproject.toml +36 -0
- namegender_client-0.3.0/setup.cfg +4 -0
- namegender_client-0.3.0/src/namegender/__init__.py +3 -0
- namegender_client-0.3.0/src/namegender/client.py +71 -0
- namegender_client-0.3.0/src/namegender_client.egg-info/PKG-INFO +77 -0
- namegender_client-0.3.0/src/namegender_client.egg-info/SOURCES.txt +10 -0
- namegender_client-0.3.0/src/namegender_client.egg-info/dependency_links.txt +1 -0
- namegender_client-0.3.0/src/namegender_client.egg-info/top_level.txt +1 -0
- namegender_client-0.3.0/tests/test_client.py +48 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
MIT License. Copyright (c) 2026 NameGender. Permission is granted to use, copy, modify, merge, publish, distribute, sublicense, and/or sell this Software, provided this notice is included. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: namegender-client
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Official Python client for the NameGender API
|
|
5
|
+
Author-email: NameGender <support@namegender.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://namegender.com
|
|
8
|
+
Project-URL: Documentation, https://namegender.com/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/anpekesen/namegender-python
|
|
10
|
+
Project-URL: Issues, https://github.com/anpekesen/namegender-python/issues
|
|
11
|
+
Keywords: gender,name,api,namegender
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# NameGender Python
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install namegender-client
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from namegender import NameGender
|
|
36
|
+
client = NameGender("YOUR_API_KEY")
|
|
37
|
+
result = client.name("Ayşe", country="TR")
|
|
38
|
+
print(result["gender"], result["probability"], result["sample_size"], result["confidence"])
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Options and response
|
|
42
|
+
|
|
43
|
+
`name`, `email`, `username` and `bulk` accept `country`, `ai_fallback` and
|
|
44
|
+
`best_guess` as keyword arguments:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
result = client.name("Andrea", country="IT", best_guess=True)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A result carries `query`, `name`, `gender`, `country`, `probability`,
|
|
51
|
+
`sample_size`, `took_ms`, `source`, `confidence` and `matched_as`, alongside
|
|
52
|
+
`credits_charged`, `credits_remaining`, `data_version` and `request_id`.
|
|
53
|
+
Success is the HTTP status: any non-2xx response raises `NameGenderError`
|
|
54
|
+
with `status` and `body` (`{"error", "message", "request_id", "docs"}`).
|
|
55
|
+
Branch on `body["error"]`, not on the message.
|
|
56
|
+
|
|
57
|
+
## Country distribution
|
|
58
|
+
|
|
59
|
+
Returns the countries a name is recorded in. This is not a country-of-origin or
|
|
60
|
+
ethnicity inference, and must not be used as one.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
result = client.countries("Mehmet", limit=10)
|
|
64
|
+
print(result["registrations"]) # [{"country": "FR", "count": 3775, "share": 58.97, "gender": "male", "probability": 99, "source": "insee"}, ...]
|
|
65
|
+
print(result["attested_in"]) # ["AL", "AU", "BE", ..., "TR", "US"]
|
|
66
|
+
print(result["basis"]["note"])
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The two lists are deliberately kept apart. `registrations` is measured volume and
|
|
70
|
+
is comparable only among the seven countries that publish counted birth
|
|
71
|
+
statistics (US, UK, France, Canada, Spain, Ireland, Norway); `share` is a
|
|
72
|
+
percentage across those counts alone. `attested_in` is presence with no weight
|
|
73
|
+
attached, which is where countries that publish no counts, such as Turkey, Japan
|
|
74
|
+
and India, appear. Show `basis["note"]` next to any percentage you display.
|
|
75
|
+
|
|
76
|
+
`limit` (1–100, default 25) caps how many counted countries come back in
|
|
77
|
+
`registrations`. One credit per request.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# NameGender Python
|
|
2
|
+
|
|
3
|
+
```sh
|
|
4
|
+
pip install namegender-client
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from namegender import NameGender
|
|
9
|
+
client = NameGender("YOUR_API_KEY")
|
|
10
|
+
result = client.name("Ayşe", country="TR")
|
|
11
|
+
print(result["gender"], result["probability"], result["sample_size"], result["confidence"])
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Options and response
|
|
15
|
+
|
|
16
|
+
`name`, `email`, `username` and `bulk` accept `country`, `ai_fallback` and
|
|
17
|
+
`best_guess` as keyword arguments:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
result = client.name("Andrea", country="IT", best_guess=True)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
A result carries `query`, `name`, `gender`, `country`, `probability`,
|
|
24
|
+
`sample_size`, `took_ms`, `source`, `confidence` and `matched_as`, alongside
|
|
25
|
+
`credits_charged`, `credits_remaining`, `data_version` and `request_id`.
|
|
26
|
+
Success is the HTTP status: any non-2xx response raises `NameGenderError`
|
|
27
|
+
with `status` and `body` (`{"error", "message", "request_id", "docs"}`).
|
|
28
|
+
Branch on `body["error"]`, not on the message.
|
|
29
|
+
|
|
30
|
+
## Country distribution
|
|
31
|
+
|
|
32
|
+
Returns the countries a name is recorded in. This is not a country-of-origin or
|
|
33
|
+
ethnicity inference, and must not be used as one.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
result = client.countries("Mehmet", limit=10)
|
|
37
|
+
print(result["registrations"]) # [{"country": "FR", "count": 3775, "share": 58.97, "gender": "male", "probability": 99, "source": "insee"}, ...]
|
|
38
|
+
print(result["attested_in"]) # ["AL", "AU", "BE", ..., "TR", "US"]
|
|
39
|
+
print(result["basis"]["note"])
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The two lists are deliberately kept apart. `registrations` is measured volume and
|
|
43
|
+
is comparable only among the seven countries that publish counted birth
|
|
44
|
+
statistics (US, UK, France, Canada, Spain, Ireland, Norway); `share` is a
|
|
45
|
+
percentage across those counts alone. `attested_in` is presence with no weight
|
|
46
|
+
attached, which is where countries that publish no counts, such as Turkey, Japan
|
|
47
|
+
and India, appear. Show `basis["note"]` next to any percentage you display.
|
|
48
|
+
|
|
49
|
+
`limit` (1–100, default 25) caps how many counted countries come back in
|
|
50
|
+
`registrations`. One credit per request.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "namegender-client"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Official Python client for the NameGender API"
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{name = "NameGender", email = "support@namegender.com"}]
|
|
13
|
+
keywords = ["gender", "name", "api", "namegender"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
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
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
26
|
+
]
|
|
27
|
+
dependencies = []
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://namegender.com"
|
|
31
|
+
Documentation = "https://namegender.com/docs"
|
|
32
|
+
Repository = "https://github.com/anpekesen/namegender-python"
|
|
33
|
+
Issues = "https://github.com/anpekesen/namegender-python/issues"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from urllib.error import HTTPError
|
|
3
|
+
from urllib.request import Request, urlopen
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NameGenderError(RuntimeError):
|
|
7
|
+
def __init__(self, message, status=0, body=None):
|
|
8
|
+
super().__init__(message)
|
|
9
|
+
self.status = status
|
|
10
|
+
self.body = body
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NameGender:
|
|
14
|
+
def __init__(self, api_key, base_url="https://namegender.com/api/v1", timeout=30):
|
|
15
|
+
if not api_key:
|
|
16
|
+
raise ValueError("api_key is required")
|
|
17
|
+
self.api_key = api_key
|
|
18
|
+
self.base_url = base_url.rstrip("/")
|
|
19
|
+
self.timeout = timeout
|
|
20
|
+
|
|
21
|
+
# ``options`` are sent as-is: ``ai_fallback=True`` falls back to a language
|
|
22
|
+
# model for names not in the database (needs AI consent on the account),
|
|
23
|
+
# ``best_guess=True`` returns the most likely gender even below the
|
|
24
|
+
# probability threshold. A successful response is any 2xx status; anything
|
|
25
|
+
# else raises NameGenderError.
|
|
26
|
+
|
|
27
|
+
def name(self, name, country=None, **options):
|
|
28
|
+
return self._post("/gender", self._payload(name=name, country=country, **options))
|
|
29
|
+
|
|
30
|
+
def email(self, email, country=None, **options):
|
|
31
|
+
return self._post("/gender/email", self._payload(email=email, country=country, **options))
|
|
32
|
+
|
|
33
|
+
def username(self, username, country=None, **options):
|
|
34
|
+
return self._post("/gender/username", self._payload(username=username, country=country, **options))
|
|
35
|
+
|
|
36
|
+
def bulk(self, names, country=None, type="name", **options):
|
|
37
|
+
return self._post("/gender/bulk", self._payload(names=list(names), country=country, type=type, **options))
|
|
38
|
+
|
|
39
|
+
def countries(self, name, limit=None):
|
|
40
|
+
"""Country distribution of a name. Not a country-of-origin or ethnicity inference.
|
|
41
|
+
|
|
42
|
+
``registrations`` is counted volume, comparable only among countries that publish
|
|
43
|
+
counted birth statistics; ``attested_in`` is presence with no weight attached.
|
|
44
|
+
"""
|
|
45
|
+
return self._post("/gender/countries", self._payload(name=name, limit=limit))
|
|
46
|
+
|
|
47
|
+
def account(self):
|
|
48
|
+
return self._request("GET", "/me")
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _payload(**values):
|
|
52
|
+
return {key: value for key, value in values.items() if value is not None}
|
|
53
|
+
|
|
54
|
+
def _post(self, path, body):
|
|
55
|
+
return self._request("POST", path, body)
|
|
56
|
+
|
|
57
|
+
def _request(self, method, path, body=None):
|
|
58
|
+
data = json.dumps(body).encode() if body is not None else None
|
|
59
|
+
request = Request(self.base_url + path, data=data, method=method, headers={
|
|
60
|
+
"Accept": "application/json", "Content-Type": "application/json",
|
|
61
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
62
|
+
})
|
|
63
|
+
try:
|
|
64
|
+
with urlopen(request, timeout=self.timeout) as response:
|
|
65
|
+
return json.loads(response.read())
|
|
66
|
+
except HTTPError as error:
|
|
67
|
+
try:
|
|
68
|
+
payload = json.loads(error.read())
|
|
69
|
+
except Exception:
|
|
70
|
+
payload = None
|
|
71
|
+
raise NameGenderError((payload or {}).get("message", str(error)), error.code, payload) from error
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: namegender-client
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Official Python client for the NameGender API
|
|
5
|
+
Author-email: NameGender <support@namegender.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://namegender.com
|
|
8
|
+
Project-URL: Documentation, https://namegender.com/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/anpekesen/namegender-python
|
|
10
|
+
Project-URL: Issues, https://github.com/anpekesen/namegender-python/issues
|
|
11
|
+
Keywords: gender,name,api,namegender
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# NameGender Python
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install namegender-client
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from namegender import NameGender
|
|
36
|
+
client = NameGender("YOUR_API_KEY")
|
|
37
|
+
result = client.name("Ayşe", country="TR")
|
|
38
|
+
print(result["gender"], result["probability"], result["sample_size"], result["confidence"])
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Options and response
|
|
42
|
+
|
|
43
|
+
`name`, `email`, `username` and `bulk` accept `country`, `ai_fallback` and
|
|
44
|
+
`best_guess` as keyword arguments:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
result = client.name("Andrea", country="IT", best_guess=True)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A result carries `query`, `name`, `gender`, `country`, `probability`,
|
|
51
|
+
`sample_size`, `took_ms`, `source`, `confidence` and `matched_as`, alongside
|
|
52
|
+
`credits_charged`, `credits_remaining`, `data_version` and `request_id`.
|
|
53
|
+
Success is the HTTP status: any non-2xx response raises `NameGenderError`
|
|
54
|
+
with `status` and `body` (`{"error", "message", "request_id", "docs"}`).
|
|
55
|
+
Branch on `body["error"]`, not on the message.
|
|
56
|
+
|
|
57
|
+
## Country distribution
|
|
58
|
+
|
|
59
|
+
Returns the countries a name is recorded in. This is not a country-of-origin or
|
|
60
|
+
ethnicity inference, and must not be used as one.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
result = client.countries("Mehmet", limit=10)
|
|
64
|
+
print(result["registrations"]) # [{"country": "FR", "count": 3775, "share": 58.97, "gender": "male", "probability": 99, "source": "insee"}, ...]
|
|
65
|
+
print(result["attested_in"]) # ["AL", "AU", "BE", ..., "TR", "US"]
|
|
66
|
+
print(result["basis"]["note"])
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The two lists are deliberately kept apart. `registrations` is measured volume and
|
|
70
|
+
is comparable only among the seven countries that publish counted birth
|
|
71
|
+
statistics (US, UK, France, Canada, Spain, Ireland, Norway); `share` is a
|
|
72
|
+
percentage across those counts alone. `attested_in` is presence with no weight
|
|
73
|
+
attached, which is where countries that publish no counts, such as Turkey, Japan
|
|
74
|
+
and India, appear. Show `basis["note"]` next to any percentage you display.
|
|
75
|
+
|
|
76
|
+
`limit` (1–100, default 25) caps how many counted countries come back in
|
|
77
|
+
`registrations`. One credit per request.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/namegender/__init__.py
|
|
5
|
+
src/namegender/client.py
|
|
6
|
+
src/namegender_client.egg-info/PKG-INFO
|
|
7
|
+
src/namegender_client.egg-info/SOURCES.txt
|
|
8
|
+
src/namegender_client.egg-info/dependency_links.txt
|
|
9
|
+
src/namegender_client.egg-info/top_level.txt
|
|
10
|
+
tests/test_client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
namegender
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import json
|
|
3
|
+
import unittest
|
|
4
|
+
from urllib.error import HTTPError
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
from namegender import NameGender, NameGenderError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ClientTest(unittest.TestCase):
|
|
10
|
+
@patch("namegender.client.urlopen")
|
|
11
|
+
def test_name(self, urlopen):
|
|
12
|
+
response = urlopen.return_value.__enter__.return_value
|
|
13
|
+
response.read.return_value = b'{"gender":"female"}'
|
|
14
|
+
result = NameGender("secret").name("Ayse", country="TR")
|
|
15
|
+
self.assertEqual(result["gender"], "female")
|
|
16
|
+
request = urlopen.call_args.args[0]
|
|
17
|
+
self.assertEqual(request.headers["Authorization"], "Bearer secret")
|
|
18
|
+
|
|
19
|
+
@patch("namegender.client.urlopen")
|
|
20
|
+
def test_countries(self, urlopen):
|
|
21
|
+
response = urlopen.return_value.__enter__.return_value
|
|
22
|
+
response.read.return_value = b'{"name":"Mehmet","registrations":[{"country":"FR","share":58.97}],"attested_in":["FR","TR"]}'
|
|
23
|
+
result = NameGender("secret").countries("Mehmet", limit=10)
|
|
24
|
+
self.assertEqual(result["attested_in"], ["FR", "TR"])
|
|
25
|
+
request = urlopen.call_args.args[0]
|
|
26
|
+
self.assertEqual(request.full_url, "https://namegender.com/api/v1/gender/countries")
|
|
27
|
+
self.assertEqual(request.get_method(), "POST")
|
|
28
|
+
self.assertEqual(request.headers["Authorization"], "Bearer secret")
|
|
29
|
+
self.assertEqual(json.loads(request.data), {"name": "Mehmet", "limit": 10})
|
|
30
|
+
|
|
31
|
+
@patch("namegender.client.urlopen")
|
|
32
|
+
def test_options_use_namegender_names(self, urlopen):
|
|
33
|
+
response = urlopen.return_value.__enter__.return_value
|
|
34
|
+
response.read.return_value = b'{"query":"Andrea","gender":"male","sample_size":120,"took_ms":3}'
|
|
35
|
+
result = NameGender("secret").name("Andrea", country="IT", ai_fallback=True, best_guess=True)
|
|
36
|
+
self.assertEqual(result["sample_size"], 120)
|
|
37
|
+
request = urlopen.call_args.args[0]
|
|
38
|
+
self.assertEqual(request.full_url, "https://namegender.com/api/v1/gender")
|
|
39
|
+
self.assertEqual(json.loads(request.data), {"name": "Andrea", "country": "IT", "ai_fallback": True, "best_guess": True})
|
|
40
|
+
|
|
41
|
+
@patch("namegender.client.urlopen")
|
|
42
|
+
def test_error_status_raises(self, urlopen):
|
|
43
|
+
body = b'{"error":"no_credits","message":"Out of credits.","request_id":"req_1","docs":"https://namegender.com/docs"}'
|
|
44
|
+
urlopen.side_effect = HTTPError("https://namegender.com/api/v1/me", 402, "Payment Required", {}, io.BytesIO(body))
|
|
45
|
+
with self.assertRaises(NameGenderError) as caught:
|
|
46
|
+
NameGender("secret").account()
|
|
47
|
+
self.assertEqual(caught.exception.status, 402)
|
|
48
|
+
self.assertEqual(caught.exception.body["error"], "no_credits")
|