demografix 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.
- demografix-0.1.0/.github/workflows/ci.yml +49 -0
- demografix-0.1.0/.github/workflows/release.yml +73 -0
- demografix-0.1.0/.gitignore +10 -0
- demografix-0.1.0/LICENSE +21 -0
- demografix-0.1.0/PKG-INFO +183 -0
- demografix-0.1.0/README.md +158 -0
- demografix-0.1.0/RELEASING.md +73 -0
- demografix-0.1.0/examples/demographics_summary.py +81 -0
- demografix-0.1.0/pyproject.toml +39 -0
- demografix-0.1.0/src/demografix/__init__.py +47 -0
- demografix-0.1.0/src/demografix/client.py +269 -0
- demografix-0.1.0/src/demografix/errors.py +55 -0
- demografix-0.1.0/src/demografix/models.py +108 -0
- demografix-0.1.0/src/demografix/py.typed +0 -0
- demografix-0.1.0/tests/test_client.py +301 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install package with dev extras
|
|
25
|
+
run: python -m pip install -e .[dev]
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: python -m pytest -q
|
|
29
|
+
|
|
30
|
+
build:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- name: Set up Python
|
|
36
|
+
uses: actions/setup-python@v5
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.x"
|
|
39
|
+
|
|
40
|
+
- name: Build sdist and wheel
|
|
41
|
+
run: |
|
|
42
|
+
python -m pip install build
|
|
43
|
+
python -m build
|
|
44
|
+
|
|
45
|
+
- name: Upload distribution artifacts
|
|
46
|
+
uses: actions/upload-artifact@v4
|
|
47
|
+
with:
|
|
48
|
+
name: dist
|
|
49
|
+
path: dist/
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Set up Python
|
|
15
|
+
uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.x"
|
|
18
|
+
|
|
19
|
+
- name: Verify tag matches manifest version
|
|
20
|
+
run: |
|
|
21
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
22
|
+
manifest="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
|
|
23
|
+
echo "Tag version: $tag"
|
|
24
|
+
echo "Manifest version: $manifest"
|
|
25
|
+
if [ "$tag" != "$manifest" ]; then
|
|
26
|
+
echo "::error::Tag version ($tag) does not match pyproject.toml version ($manifest)."
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
- name: Build sdist and wheel
|
|
31
|
+
run: |
|
|
32
|
+
python -m pip install build
|
|
33
|
+
python -m build
|
|
34
|
+
|
|
35
|
+
- name: Upload distribution artifacts
|
|
36
|
+
uses: actions/upload-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: dist
|
|
39
|
+
path: dist/
|
|
40
|
+
|
|
41
|
+
publish-pypi:
|
|
42
|
+
needs: build
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
environment: release
|
|
45
|
+
permissions:
|
|
46
|
+
id-token: write
|
|
47
|
+
steps:
|
|
48
|
+
- name: Download distribution artifacts
|
|
49
|
+
uses: actions/download-artifact@v4
|
|
50
|
+
with:
|
|
51
|
+
name: dist
|
|
52
|
+
path: dist/
|
|
53
|
+
|
|
54
|
+
- name: Publish to PyPI via Trusted Publishing
|
|
55
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
56
|
+
|
|
57
|
+
github-release:
|
|
58
|
+
needs: publish-pypi
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
permissions:
|
|
61
|
+
contents: write
|
|
62
|
+
steps:
|
|
63
|
+
- name: Download distribution artifacts
|
|
64
|
+
uses: actions/download-artifact@v4
|
|
65
|
+
with:
|
|
66
|
+
name: dist
|
|
67
|
+
path: dist/
|
|
68
|
+
|
|
69
|
+
- name: Create GitHub Release
|
|
70
|
+
uses: softprops/action-gh-release@v2
|
|
71
|
+
with:
|
|
72
|
+
files: dist/*
|
|
73
|
+
generate_release_notes: true
|
demografix-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Demografix
|
|
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.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: demografix
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Demografix APIs: genderize.io, agify.io, nationalize.io.
|
|
5
|
+
Project-URL: Homepage, https://genderize.io
|
|
6
|
+
Project-URL: Documentation, https://genderize.io/documentation/api
|
|
7
|
+
Project-URL: Repository, https://github.com/DemografixGenderize/demografix-python
|
|
8
|
+
Project-URL: Issues, https://github.com/DemografixGenderize/demografix-python/issues
|
|
9
|
+
Author-email: Demografix <info@genderize.io>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agify,demografix,demographics,genderize,nationalize
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# Demografix Python SDK
|
|
27
|
+
|
|
28
|
+
Run demographic analysis over names — predicted gender, age, and nationality — from one client. The package
|
|
29
|
+
covers [genderize.io](https://genderize.io), [agify.io](https://agify.io), and
|
|
30
|
+
[nationalize.io](https://nationalize.io).
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install demografix
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The SDK has zero runtime dependencies. It requires Python 3.10 or newer.
|
|
39
|
+
|
|
40
|
+
## Quickstart
|
|
41
|
+
|
|
42
|
+
Construct a client, run a list of names through a batch call, read the predictions, and read the quota.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from collections import Counter
|
|
46
|
+
from demografix import Demografix
|
|
47
|
+
|
|
48
|
+
client = Demografix(api_key="YOUR_API_KEY")
|
|
49
|
+
|
|
50
|
+
names = ["michael", "matthew", "jane", "sofia", "lars"]
|
|
51
|
+
|
|
52
|
+
batch = client.genderize_batch(names)
|
|
53
|
+
|
|
54
|
+
split = Counter(r.gender or "unknown" for r in batch.results)
|
|
55
|
+
print(split) # Counter({'male': 3, 'female': 2})
|
|
56
|
+
print(batch.quota.remaining) # 24987
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## API keys
|
|
60
|
+
|
|
61
|
+
An API key is required. Creating one is free and includes 2,500 requests per month. Generate a key in your
|
|
62
|
+
dashboard at [genderize.io](https://genderize.io), [agify.io](https://agify.io), or
|
|
63
|
+
[nationalize.io](https://nationalize.io). One key works across all three services.
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### Gender
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
result = client.genderize("peter")
|
|
71
|
+
result.gender # "male", "female", or None
|
|
72
|
+
result.probability # 1.0
|
|
73
|
+
result.count # 1352696
|
|
74
|
+
|
|
75
|
+
batch = client.genderize_batch(["michael", "matthew", "jane"])
|
|
76
|
+
gender_mix = Counter(r.gender or "unknown" for r in batch.results)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Age
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
result = client.agify("michael")
|
|
83
|
+
result.age # 57 or None
|
|
84
|
+
result.count # 311558
|
|
85
|
+
|
|
86
|
+
batch = client.agify_batch(["michael", "matthew", "jane"])
|
|
87
|
+
ages = [r.age for r in batch.results if r.age is not None]
|
|
88
|
+
average_age = sum(ages) / len(ages)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Nationality
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
result = client.nationalize("nguyen")
|
|
95
|
+
result.country[0].country_id # "VN"
|
|
96
|
+
result.country[0].probability # 0.891132
|
|
97
|
+
|
|
98
|
+
batch = client.nationalize_batch(["nguyen", "schmidt", "rossi"])
|
|
99
|
+
top_countries = Counter(
|
|
100
|
+
r.country[0].country_id for r in batch.results if r.country
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Each batch accepts at most 10 names. A batch of more than 10 raises `ValidationError` before any request
|
|
105
|
+
goes out.
|
|
106
|
+
|
|
107
|
+
## country_id
|
|
108
|
+
|
|
109
|
+
`genderize` and `agify` accept an optional `country_id` (ISO 3166-1 alpha-2) to scope the prediction to one
|
|
110
|
+
country. Input is case-insensitive; the response echoes it uppercase. `nationalize` has no such parameter.
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
result = client.genderize("kim", country_id="us")
|
|
114
|
+
result.country_id # "US"
|
|
115
|
+
|
|
116
|
+
batch = client.agify_batch(["michael", "matthew"], country_id="us")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Quota
|
|
120
|
+
|
|
121
|
+
Every result and every raised error carries a `quota` read from the response headers. Quota is never cached
|
|
122
|
+
on the client; read it from the returned value.
|
|
123
|
+
|
|
124
|
+
| Field | Meaning |
|
|
125
|
+
|---|---|
|
|
126
|
+
| `limit` | names allowed in the current window |
|
|
127
|
+
| `remaining` | names left in the current window |
|
|
128
|
+
| `reset` | seconds until the window resets |
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
batch = client.genderize_batch(["michael", "matthew"])
|
|
132
|
+
batch.quota.remaining
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Errors
|
|
136
|
+
|
|
137
|
+
Non-2xx responses raise a typed exception. Transport failures raise `TransportError`. Every exception
|
|
138
|
+
subclasses `DemografixError` and carries `status`, `message`, and `quota` (when the response included
|
|
139
|
+
headers).
|
|
140
|
+
|
|
141
|
+
| Exception | Status | Cause |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| `AuthError` | 401 | invalid or missing API key |
|
|
144
|
+
| `SubscriptionError` | 402 | expired freebie or inactive subscription |
|
|
145
|
+
| `ValidationError` | 422 | bad parameter, or a batch over 10 names (raised client-side) |
|
|
146
|
+
| `RateLimitError` | 429 | window exhausted; `quota` is always populated |
|
|
147
|
+
| `DemografixError` | other non-2xx | base class for the hierarchy |
|
|
148
|
+
| `TransportError` | none | network error, timeout, or non-JSON body |
|
|
149
|
+
|
|
150
|
+
A `RateLimitError` carries `quota`, so `reset` tells you how long to wait before retrying.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import time
|
|
154
|
+
from demografix import Demografix, RateLimitError
|
|
155
|
+
|
|
156
|
+
client = Demografix(api_key="YOUR_API_KEY")
|
|
157
|
+
names = ["michael", "matthew", "jane"]
|
|
158
|
+
|
|
159
|
+
while True:
|
|
160
|
+
try:
|
|
161
|
+
batch = client.genderize_batch(names)
|
|
162
|
+
break
|
|
163
|
+
except RateLimitError as exc:
|
|
164
|
+
time.sleep(exc.quota.reset)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Methods
|
|
168
|
+
|
|
169
|
+
| Method | Returns | country_id |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `genderize(name, country_id=None)` | `GenderizeResult` | yes |
|
|
172
|
+
| `genderize_batch(names, country_id=None)` | `Batch` of `GenderizePrediction` | yes |
|
|
173
|
+
| `agify(name, country_id=None)` | `AgifyResult` | yes |
|
|
174
|
+
| `agify_batch(names, country_id=None)` | `Batch` of `AgifyPrediction` | yes |
|
|
175
|
+
| `nationalize(name)` | `NationalizeResult` | no |
|
|
176
|
+
| `nationalize_batch(names)` | `Batch` of `NationalizePrediction` | no |
|
|
177
|
+
|
|
178
|
+
A `*Result` exposes the prediction fields directly plus a `quota`. A `Batch` exposes `results` plus one
|
|
179
|
+
`quota` for the whole response.
|
|
180
|
+
|
|
181
|
+
## Reference
|
|
182
|
+
|
|
183
|
+
Full API reference: [genderize.io/documentation/api](https://genderize.io/documentation/api).
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Demografix Python SDK
|
|
2
|
+
|
|
3
|
+
Run demographic analysis over names — predicted gender, age, and nationality — from one client. The package
|
|
4
|
+
covers [genderize.io](https://genderize.io), [agify.io](https://agify.io), and
|
|
5
|
+
[nationalize.io](https://nationalize.io).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
pip install demografix
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The SDK has zero runtime dependencies. It requires Python 3.10 or newer.
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
Construct a client, run a list of names through a batch call, read the predictions, and read the quota.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from collections import Counter
|
|
21
|
+
from demografix import Demografix
|
|
22
|
+
|
|
23
|
+
client = Demografix(api_key="YOUR_API_KEY")
|
|
24
|
+
|
|
25
|
+
names = ["michael", "matthew", "jane", "sofia", "lars"]
|
|
26
|
+
|
|
27
|
+
batch = client.genderize_batch(names)
|
|
28
|
+
|
|
29
|
+
split = Counter(r.gender or "unknown" for r in batch.results)
|
|
30
|
+
print(split) # Counter({'male': 3, 'female': 2})
|
|
31
|
+
print(batch.quota.remaining) # 24987
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API keys
|
|
35
|
+
|
|
36
|
+
An API key is required. Creating one is free and includes 2,500 requests per month. Generate a key in your
|
|
37
|
+
dashboard at [genderize.io](https://genderize.io), [agify.io](https://agify.io), or
|
|
38
|
+
[nationalize.io](https://nationalize.io). One key works across all three services.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Gender
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
result = client.genderize("peter")
|
|
46
|
+
result.gender # "male", "female", or None
|
|
47
|
+
result.probability # 1.0
|
|
48
|
+
result.count # 1352696
|
|
49
|
+
|
|
50
|
+
batch = client.genderize_batch(["michael", "matthew", "jane"])
|
|
51
|
+
gender_mix = Counter(r.gender or "unknown" for r in batch.results)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Age
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
result = client.agify("michael")
|
|
58
|
+
result.age # 57 or None
|
|
59
|
+
result.count # 311558
|
|
60
|
+
|
|
61
|
+
batch = client.agify_batch(["michael", "matthew", "jane"])
|
|
62
|
+
ages = [r.age for r in batch.results if r.age is not None]
|
|
63
|
+
average_age = sum(ages) / len(ages)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Nationality
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
result = client.nationalize("nguyen")
|
|
70
|
+
result.country[0].country_id # "VN"
|
|
71
|
+
result.country[0].probability # 0.891132
|
|
72
|
+
|
|
73
|
+
batch = client.nationalize_batch(["nguyen", "schmidt", "rossi"])
|
|
74
|
+
top_countries = Counter(
|
|
75
|
+
r.country[0].country_id for r in batch.results if r.country
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Each batch accepts at most 10 names. A batch of more than 10 raises `ValidationError` before any request
|
|
80
|
+
goes out.
|
|
81
|
+
|
|
82
|
+
## country_id
|
|
83
|
+
|
|
84
|
+
`genderize` and `agify` accept an optional `country_id` (ISO 3166-1 alpha-2) to scope the prediction to one
|
|
85
|
+
country. Input is case-insensitive; the response echoes it uppercase. `nationalize` has no such parameter.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
result = client.genderize("kim", country_id="us")
|
|
89
|
+
result.country_id # "US"
|
|
90
|
+
|
|
91
|
+
batch = client.agify_batch(["michael", "matthew"], country_id="us")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Quota
|
|
95
|
+
|
|
96
|
+
Every result and every raised error carries a `quota` read from the response headers. Quota is never cached
|
|
97
|
+
on the client; read it from the returned value.
|
|
98
|
+
|
|
99
|
+
| Field | Meaning |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `limit` | names allowed in the current window |
|
|
102
|
+
| `remaining` | names left in the current window |
|
|
103
|
+
| `reset` | seconds until the window resets |
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
batch = client.genderize_batch(["michael", "matthew"])
|
|
107
|
+
batch.quota.remaining
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Errors
|
|
111
|
+
|
|
112
|
+
Non-2xx responses raise a typed exception. Transport failures raise `TransportError`. Every exception
|
|
113
|
+
subclasses `DemografixError` and carries `status`, `message`, and `quota` (when the response included
|
|
114
|
+
headers).
|
|
115
|
+
|
|
116
|
+
| Exception | Status | Cause |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| `AuthError` | 401 | invalid or missing API key |
|
|
119
|
+
| `SubscriptionError` | 402 | expired freebie or inactive subscription |
|
|
120
|
+
| `ValidationError` | 422 | bad parameter, or a batch over 10 names (raised client-side) |
|
|
121
|
+
| `RateLimitError` | 429 | window exhausted; `quota` is always populated |
|
|
122
|
+
| `DemografixError` | other non-2xx | base class for the hierarchy |
|
|
123
|
+
| `TransportError` | none | network error, timeout, or non-JSON body |
|
|
124
|
+
|
|
125
|
+
A `RateLimitError` carries `quota`, so `reset` tells you how long to wait before retrying.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import time
|
|
129
|
+
from demografix import Demografix, RateLimitError
|
|
130
|
+
|
|
131
|
+
client = Demografix(api_key="YOUR_API_KEY")
|
|
132
|
+
names = ["michael", "matthew", "jane"]
|
|
133
|
+
|
|
134
|
+
while True:
|
|
135
|
+
try:
|
|
136
|
+
batch = client.genderize_batch(names)
|
|
137
|
+
break
|
|
138
|
+
except RateLimitError as exc:
|
|
139
|
+
time.sleep(exc.quota.reset)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Methods
|
|
143
|
+
|
|
144
|
+
| Method | Returns | country_id |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `genderize(name, country_id=None)` | `GenderizeResult` | yes |
|
|
147
|
+
| `genderize_batch(names, country_id=None)` | `Batch` of `GenderizePrediction` | yes |
|
|
148
|
+
| `agify(name, country_id=None)` | `AgifyResult` | yes |
|
|
149
|
+
| `agify_batch(names, country_id=None)` | `Batch` of `AgifyPrediction` | yes |
|
|
150
|
+
| `nationalize(name)` | `NationalizeResult` | no |
|
|
151
|
+
| `nationalize_batch(names)` | `Batch` of `NationalizePrediction` | no |
|
|
152
|
+
|
|
153
|
+
A `*Result` exposes the prediction fields directly plus a `quota`. A `Batch` exposes `results` plus one
|
|
154
|
+
`quota` for the whole response.
|
|
155
|
+
|
|
156
|
+
## Reference
|
|
157
|
+
|
|
158
|
+
Full API reference: [genderize.io/documentation/api](https://genderize.io/documentation/api).
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
This package publishes to PyPI through GitHub Actions. Publishing uses PyPI
|
|
4
|
+
Trusted Publishing (OIDC), so there is no API token to store or rotate. The
|
|
5
|
+
`release` workflow runs on any pushed tag that matches `v*.*.*`.
|
|
6
|
+
|
|
7
|
+
## One-time setup
|
|
8
|
+
|
|
9
|
+
Do this once, before the first release.
|
|
10
|
+
|
|
11
|
+
### 1. Reserve the project name on PyPI
|
|
12
|
+
|
|
13
|
+
The project name `demografix` must exist on PyPI, or be claimable by the
|
|
14
|
+
account that registers the Trusted Publisher. Confirm the name is available at
|
|
15
|
+
https://pypi.org/project/demografix/ before going further.
|
|
16
|
+
|
|
17
|
+
### 2. Register the Trusted Publisher on PyPI
|
|
18
|
+
|
|
19
|
+
In the PyPI project settings, under "Publishing", add a new GitHub trusted
|
|
20
|
+
publisher with these exact values:
|
|
21
|
+
|
|
22
|
+
- Owner: `DemografixGenderize`
|
|
23
|
+
- Repository: `demografix-python`
|
|
24
|
+
- Workflow name: `release.yml`
|
|
25
|
+
- Environment: `release`
|
|
26
|
+
|
|
27
|
+
If the project does not exist on PyPI yet, register the publisher as a
|
|
28
|
+
"pending" publisher instead. PyPI creates the project on the first successful
|
|
29
|
+
upload.
|
|
30
|
+
|
|
31
|
+
### 3. Create the `release` environment in GitHub
|
|
32
|
+
|
|
33
|
+
In the repository settings, under "Environments", create an environment named
|
|
34
|
+
`release`. The publish job references this environment. Add required reviewers
|
|
35
|
+
or branch restrictions here if you want a manual gate before any upload.
|
|
36
|
+
|
|
37
|
+
No secrets are required. Trusted Publishing exchanges a short-lived OIDC token
|
|
38
|
+
for the upload credential at publish time. Do not add a `PYPI_API_TOKEN` or any
|
|
39
|
+
password.
|
|
40
|
+
|
|
41
|
+
## Cutting a release
|
|
42
|
+
|
|
43
|
+
1. Bump `version` in `pyproject.toml` to the new `X.Y.Z`.
|
|
44
|
+
2. Commit the bump:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
git commit -am "Release vX.Y.Z"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
3. Tag the commit. The tag must match the manifest version, with a `v` prefix:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
git tag vX.Y.Z
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
4. Push the commit and the tag:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
git push origin main
|
|
60
|
+
git push origin vX.Y.Z
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Pushing the tag starts the `release` workflow. It verifies the tag matches the
|
|
64
|
+
`pyproject.toml` version, builds the sdist and wheel, publishes to PyPI, and
|
|
65
|
+
creates a GitHub Release with the built artifacts attached.
|
|
66
|
+
|
|
67
|
+
## If a release fails
|
|
68
|
+
|
|
69
|
+
- Tag/version mismatch: the build job stops before publishing. Delete the tag
|
|
70
|
+
(`git tag -d vX.Y.Z` and `git push origin :vX.Y.Z`), fix the version, and tag
|
|
71
|
+
again.
|
|
72
|
+
- PyPI rejects the upload because the version already exists: bump to a new
|
|
73
|
+
version and tag again. PyPI does not allow re-uploading an existing version.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Summarize the demographic mix of a list of names.
|
|
2
|
+
|
|
3
|
+
Run a roster through all three services in batches of 10 and print an aggregate
|
|
4
|
+
gender split, age distribution, and nationality mix across the whole list.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
DEMOGRAFIX_API_KEY=your_key python examples/demographics_summary.py
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from collections import Counter
|
|
12
|
+
|
|
13
|
+
from demografix import Demografix
|
|
14
|
+
|
|
15
|
+
NAMES = [
|
|
16
|
+
"michael",
|
|
17
|
+
"matthew",
|
|
18
|
+
"jane",
|
|
19
|
+
"nguyen",
|
|
20
|
+
"kim",
|
|
21
|
+
"sofia",
|
|
22
|
+
"lars",
|
|
23
|
+
"amara",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def chunked(items, size):
|
|
28
|
+
for start in range(0, len(items), size):
|
|
29
|
+
yield items[start : start + size]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main():
|
|
33
|
+
api_key = os.environ.get("DEMOGRAFIX_API_KEY")
|
|
34
|
+
if not api_key:
|
|
35
|
+
raise SystemExit("Set DEMOGRAFIX_API_KEY to run this example.")
|
|
36
|
+
client = Demografix(api_key=api_key)
|
|
37
|
+
|
|
38
|
+
genders = Counter()
|
|
39
|
+
ages = []
|
|
40
|
+
countries = Counter()
|
|
41
|
+
remaining = None
|
|
42
|
+
|
|
43
|
+
for chunk in chunked(NAMES, 10):
|
|
44
|
+
g = client.genderize_batch(chunk)
|
|
45
|
+
a = client.agify_batch(chunk)
|
|
46
|
+
n = client.nationalize_batch(chunk)
|
|
47
|
+
remaining = n.quota.remaining
|
|
48
|
+
|
|
49
|
+
for prediction in g.results:
|
|
50
|
+
genders[prediction.gender or "unknown"] += 1
|
|
51
|
+
for prediction in a.results:
|
|
52
|
+
if prediction.age is not None:
|
|
53
|
+
ages.append(prediction.age)
|
|
54
|
+
for prediction in n.results:
|
|
55
|
+
if prediction.country:
|
|
56
|
+
countries[prediction.country[0].country_id] += 1
|
|
57
|
+
|
|
58
|
+
total = len(NAMES)
|
|
59
|
+
print("Names analyzed: %d" % total)
|
|
60
|
+
|
|
61
|
+
print("\nGender split:")
|
|
62
|
+
for gender, count in genders.most_common():
|
|
63
|
+
print(" %-8s %d (%.0f%%)" % (gender, count, 100 * count / total))
|
|
64
|
+
|
|
65
|
+
if ages:
|
|
66
|
+
print("\nAge distribution:")
|
|
67
|
+
print(" count %d" % len(ages))
|
|
68
|
+
print(" min %d" % min(ages))
|
|
69
|
+
print(" median %d" % sorted(ages)[len(ages) // 2])
|
|
70
|
+
print(" max %d" % max(ages))
|
|
71
|
+
|
|
72
|
+
print("\nTop nationality mix:")
|
|
73
|
+
for country_id, count in countries.most_common(5):
|
|
74
|
+
print(" %-4s %d" % (country_id, count))
|
|
75
|
+
|
|
76
|
+
if remaining is not None:
|
|
77
|
+
print("\nQuota remaining: %d" % remaining)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
main()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "demografix"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the Demografix APIs: genderize.io, agify.io, nationalize.io."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Demografix", email = "info@genderize.io" }]
|
|
14
|
+
keywords = ["genderize", "agify", "nationalize", "demografix", "demographics"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
]
|
|
25
|
+
dependencies = []
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = ["pytest>=7"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://genderize.io"
|
|
32
|
+
Documentation = "https://genderize.io/documentation/api"
|
|
33
|
+
Repository = "https://github.com/DemografixGenderize/demografix-python"
|
|
34
|
+
Issues = "https://github.com/DemografixGenderize/demografix-python/issues"
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.wheel]
|
|
37
|
+
packages = ["src/demografix"]
|
|
38
|
+
# PEP 561: ship the py.typed marker so type checkers pick up the inline types.
|
|
39
|
+
force-include = { "src/demografix/py.typed" = "demografix/py.typed" }
|