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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .client import NameGender, NameGenderError
2
+
3
+ __all__ = ["NameGender", "NameGenderError"]
@@ -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,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")