allowances-api 0.1.0__py3-none-any.whl

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,26 @@
1
+ """Python SDK for the Allowances API."""
2
+
3
+ from .client import AllowancesClient
4
+ from .exceptions import (
5
+ AllowancesAPIError,
6
+ AuthenticationError,
7
+ ForbiddenError,
8
+ NotFoundError,
9
+ RateLimitError,
10
+ TransportError,
11
+ ValidationError,
12
+ )
13
+
14
+ __all__ = [
15
+ "AllowancesClient",
16
+ "AllowancesAPIError",
17
+ "AuthenticationError",
18
+ "ForbiddenError",
19
+ "NotFoundError",
20
+ "RateLimitError",
21
+ "TransportError",
22
+ "ValidationError",
23
+ ]
24
+
25
+ __version__ = "0.1.0"
26
+
@@ -0,0 +1,24 @@
1
+ from datetime import date, datetime
2
+ from enum import Enum
3
+ from typing import Any, Dict, Mapping, Optional
4
+
5
+
6
+ def serialize(value: Any) -> Any:
7
+ if isinstance(value, (date, datetime)):
8
+ return value.isoformat()
9
+ if isinstance(value, Enum):
10
+ return value.value
11
+ if isinstance(value, (list, tuple, set)):
12
+ return [serialize(item) for item in value]
13
+ return value
14
+
15
+
16
+ def query_params(**values: Any) -> Dict[str, Any]:
17
+ return {key: serialize(value) for key, value in values.items() if value is not None}
18
+
19
+
20
+ def merge_params(params: Optional[Mapping[str, Any]], **values: Any) -> Dict[str, Any]:
21
+ merged = dict(params or {})
22
+ merged.update(query_params(**values))
23
+ return merged
24
+
@@ -0,0 +1,100 @@
1
+ """HTTP client and authentication for the Allowances API."""
2
+
3
+ import os
4
+ from typing import Any, Mapping, Optional
5
+
6
+ import httpx
7
+
8
+ from .exceptions import (
9
+ AllowancesAPIError,
10
+ AuthenticationError,
11
+ ForbiddenError,
12
+ NotFoundError,
13
+ RateLimitError,
14
+ TransportError,
15
+ ValidationError,
16
+ error_details,
17
+ )
18
+ from .resources import DSSRResource, DTMOResource, GSAResource
19
+
20
+ DEFAULT_BASE_URL = "https://api.allowancesapi.com/v1"
21
+
22
+
23
+ class AllowancesClient:
24
+ """Authenticated synchronous client for all public Allowances API endpoints."""
25
+
26
+ def __init__(
27
+ self,
28
+ api_key: Optional[str] = None,
29
+ *,
30
+ base_url: str = DEFAULT_BASE_URL,
31
+ timeout: float = 30.0,
32
+ http_client: Optional[httpx.Client] = None,
33
+ ) -> None:
34
+ key = api_key or os.getenv("ALLOWANCES_API_KEY")
35
+ if not key:
36
+ raise ValueError("Pass api_key or set the ALLOWANCES_API_KEY environment variable")
37
+
38
+ self._owns_client = http_client is None
39
+ self._http = http_client or httpx.Client(
40
+ base_url=base_url.rstrip("/"),
41
+ timeout=timeout,
42
+ headers={"X-API-Key": key, "User-Agent": "allowances-api-python/0.1.0"},
43
+ )
44
+ if http_client is not None:
45
+ self._http.headers["X-API-Key"] = key
46
+ self._http.headers.setdefault("User-Agent", "allowances-api-python/0.1.0")
47
+
48
+ self.dssr = DSSRResource(self)
49
+ self.dtmo = DTMOResource(self)
50
+ self.gsa = GSAResource(self)
51
+
52
+ def request(
53
+ self,
54
+ method: str,
55
+ path: str,
56
+ *,
57
+ params: Optional[Mapping[str, Any]] = None,
58
+ ) -> Any:
59
+ try:
60
+ response = self._http.request(method, path, params=params)
61
+ except httpx.HTTPError as exc:
62
+ raise TransportError(str(exc)) from exc
63
+
64
+ if response.is_success:
65
+ if response.status_code == 204 or not response.content:
66
+ return None
67
+ return response.json()
68
+
69
+ try:
70
+ payload = response.json()
71
+ except ValueError:
72
+ payload = None
73
+ message, request_id = error_details(payload)
74
+ error_type = {
75
+ 401: AuthenticationError,
76
+ 403: ForbiddenError,
77
+ 404: NotFoundError,
78
+ 422: ValidationError,
79
+ 429: RateLimitError,
80
+ }.get(response.status_code, AllowancesAPIError)
81
+ raise error_type(
82
+ message,
83
+ status_code=response.status_code,
84
+ request_id=request_id,
85
+ response=response,
86
+ )
87
+
88
+ def get(self, path: str, *, params: Optional[Mapping[str, Any]] = None) -> Any:
89
+ return self.request("GET", path, params=params)
90
+
91
+ def close(self) -> None:
92
+ if self._owns_client:
93
+ self._http.close()
94
+
95
+ def __enter__(self) -> "AllowancesClient":
96
+ return self
97
+
98
+ def __exit__(self, *args: Any) -> None:
99
+ self.close()
100
+
@@ -0,0 +1,60 @@
1
+ """Exceptions raised by the Allowances API client."""
2
+
3
+ from typing import Any, Mapping, Optional
4
+
5
+
6
+ class AllowancesAPIError(Exception):
7
+ """An error response returned by the Allowances API."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ *,
13
+ status_code: Optional[int] = None,
14
+ request_id: Optional[str] = None,
15
+ response: Any = None,
16
+ ) -> None:
17
+ super().__init__(message)
18
+ self.message = message
19
+ self.status_code = status_code
20
+ self.request_id = request_id
21
+ self.response = response
22
+
23
+
24
+ class AuthenticationError(AllowancesAPIError):
25
+ """The API key is missing or invalid."""
26
+
27
+
28
+ class ForbiddenError(AllowancesAPIError):
29
+ """The current plan cannot access the requested resource."""
30
+
31
+
32
+ class NotFoundError(AllowancesAPIError):
33
+ """The requested data was not found."""
34
+
35
+
36
+ class ValidationError(AllowancesAPIError):
37
+ """One or more request parameters are invalid."""
38
+
39
+
40
+ class RateLimitError(AllowancesAPIError):
41
+ """The account's request limit was exceeded."""
42
+
43
+
44
+ class TransportError(AllowancesAPIError):
45
+ """The request could not reach the API."""
46
+
47
+
48
+ def error_details(payload: Any) -> tuple[str, Optional[str]]:
49
+ """Extract the API's message and request ID from supported error envelopes."""
50
+ if not isinstance(payload, Mapping):
51
+ return "Allowances API request failed", None
52
+
53
+ detail = payload.get("detail", payload)
54
+ if isinstance(detail, Mapping):
55
+ return str(detail.get("message") or detail.get("detail") or detail), detail.get("request_id")
56
+ if isinstance(detail, list):
57
+ messages = [str(item.get("msg", item)) if isinstance(item, Mapping) else str(item) for item in detail]
58
+ return "; ".join(messages), None
59
+ return str(detail), None
60
+
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,153 @@
1
+ """Resource clients mirroring the public v1 API routes."""
2
+
3
+ from datetime import date
4
+ from typing import TYPE_CHECKING, Any, Iterable, Optional, Union
5
+ from urllib.parse import quote
6
+
7
+ from ._utils import query_params
8
+
9
+ if TYPE_CHECKING:
10
+ from .client import AllowancesClient
11
+
12
+ DateLike = Union[str, date]
13
+
14
+
15
+ def segment(value: Any) -> str:
16
+ return quote(str(value), safe="")
17
+
18
+
19
+ class Resource:
20
+ def __init__(self, client: "AllowancesClient") -> None:
21
+ self._client = client
22
+
23
+ def _get(self, path: str, **params: Any) -> Any:
24
+ # Friendly Python names for API parameters whose wire names are terse or singular.
25
+ if "types" in params:
26
+ params["type"] = params.pop("types")
27
+ if "query" in params:
28
+ params["q"] = params.pop("query")
29
+ return self._client.get(path, params=query_params(**params))
30
+
31
+
32
+ class DSSRResource(Resource):
33
+ def allowances(
34
+ self, country: str, *, date: Optional[DateLike] = None,
35
+ types: Optional[Iterable[str]] = None, verification_url: bool = False,
36
+ ) -> Any:
37
+ return self._get(f"/dssr/allowances/{segment(country)}", date=date, type=types,
38
+ verification_url=verification_url)
39
+
40
+ def allowances_by_location(self, country: str, location: str, **params: Any) -> Any:
41
+ return self._get(f"/dssr/allowances/{segment(country)}/location/{segment(location)}", **params)
42
+
43
+ def allowances_by_postcode(self, country: str, postcode: Union[str, int], **params: Any) -> Any:
44
+ return self._get(f"/dssr/allowances/{segment(country)}/postcode/{segment(postcode)}", **params)
45
+
46
+ def perdiem(self, country: str, **params: Any) -> Any:
47
+ return self._get(f"/dssr/perdiem/{segment(country)}", **params)
48
+
49
+ def perdiem_by_location(self, country: str, location: str, **params: Any) -> Any:
50
+ return self._get(f"/dssr/perdiem/{segment(country)}/location/{segment(location)}", **params)
51
+
52
+ def perdiem_by_postcode(self, country: str, postcode: Union[str, int], **params: Any) -> Any:
53
+ return self._get(f"/dssr/perdiem/{segment(country)}/postcode/{segment(postcode)}", **params)
54
+
55
+ def search_perdiem(self, query: str, *, date: Optional[DateLike] = None) -> Any:
56
+ return self._get("/dssr/perdiem/search", q=query, date=date)
57
+
58
+ def locations(self, *, query: Optional[str] = None, offset: int = 0, limit: int = 10) -> Any:
59
+ return self._get("/dssr/locations", q=query, offset=offset, limit=limit)
60
+
61
+ def countries(self, *, query: Optional[str] = None, offset: int = 0, limit: int = 20) -> Any:
62
+ return self._get("/dssr/countries", q=query, offset=offset, limit=limit)
63
+
64
+ def country(self, country: str) -> Any:
65
+ return self._get(f"/dssr/country/{segment(country)}")
66
+
67
+ def country_locations(self, country: str) -> Any:
68
+ return self._get(f"/dssr/country/{segment(country)}/locations")
69
+
70
+ def calculate_cola(
71
+ self, postcode: Union[str, int], *, family_size: int, base_salary: int,
72
+ date: Optional[DateLike] = None,
73
+ ) -> Any:
74
+ return self._get(f"/dssr/cola-calculator/postcode/{segment(postcode)}",
75
+ family_size=family_size, base_salary=base_salary, date=date)
76
+
77
+
78
+ class DTMOResource(Resource):
79
+ def perdiem_conus(
80
+ self, state: str, *, date: Optional[DateLike] = None, year: Optional[int] = None,
81
+ query: Optional[str] = None, verification_url: bool = False,
82
+ offset: int = 0, limit: int = 50,
83
+ ) -> Any:
84
+ return self._get(f"/dtmo/perdiem/conus/{segment(state)}", date=date, year=year, q=query,
85
+ verification_url=verification_url, offset=offset, limit=limit)
86
+
87
+ def perdiem_oconus(self, country: str, **params: Any) -> Any:
88
+ return self._get(f"/dtmo/perdiem/oconus/{segment(country)}", **params)
89
+
90
+ def search_perdiem(
91
+ self, query: str, *, date: Optional[DateLike] = None, year: Optional[int] = None,
92
+ verification_url: bool = False,
93
+ ) -> Any:
94
+ return self._get("/dtmo/perdiem/search", q=query, date=date, year=year,
95
+ verification_url=verification_url)
96
+
97
+ def locations_conus(self, state: str) -> Any:
98
+ return self._get(f"/dtmo/locations/conus/{segment(state)}")
99
+
100
+ def locations_oconus(self, country: str, *, location: Optional[str] = None) -> Any:
101
+ return self._get(f"/dtmo/locations/oconus/{segment(country)}", location=location)
102
+
103
+
104
+ class GSAResource(Resource):
105
+ def perdiem_by_destination(self, destination_id: int, **params: Any) -> Any:
106
+ return self._get(f"/gsa/perdiem/destination/{destination_id}", **params)
107
+
108
+ def perdiem_by_state(self, state: str, **params: Any) -> Any:
109
+ return self._get(f"/gsa/perdiem/state/{segment(state)}", **params)
110
+
111
+ def perdiem_by_state_year(self, state: str, year: int, **params: Any) -> Any:
112
+ return self._get(f"/gsa/perdiem/state/{segment(state)}/year/{year}", **params)
113
+
114
+ def perdiem_by_zipcode(self, zipcode: Union[str, int], **params: Any) -> Any:
115
+ return self._get(f"/gsa/perdiem/zipcode/{segment(zipcode)}", **params)
116
+
117
+ def perdiem_by_zipcode_year(self, zipcode: Union[str, int], year: int, **params: Any) -> Any:
118
+ return self._get(f"/gsa/perdiem/zipcode/{segment(zipcode)}/year/{year}", **params)
119
+
120
+ def zipcodes(self, state: str, *, offset: int = 0, limit: int = 20) -> Any:
121
+ return self._get(f"/gsa/zipcodes/{segment(state)}", offset=offset, limit=limit)
122
+
123
+ def zipcode(self, zipcode: Union[str, int]) -> Any:
124
+ return self._get(f"/gsa/zipcode/{segment(zipcode)}")
125
+
126
+ def counties(
127
+ self, *, query: Optional[str] = None, state: Optional[str] = None,
128
+ offset: int = 0, limit: int = 20,
129
+ ) -> Any:
130
+ return self._get("/gsa/counties", q=query, state=state, offset=offset, limit=limit)
131
+
132
+ def destination(self, destination_id: int) -> Any:
133
+ return self._get(f"/gsa/destination/{destination_id}")
134
+
135
+ def destinations(self, *, offset: int = 0, limit: int = 100) -> Any:
136
+ return self._get("/gsa/destinations", offset=offset, limit=limit)
137
+
138
+ def states(self) -> Any:
139
+ return self._get("/gsa/states")
140
+
141
+ def mileage(
142
+ self, *, date: Optional[DateLike] = None, mileage_type: Optional[str] = None,
143
+ verification_url: bool = False,
144
+ ) -> Any:
145
+ return self._get("/gsa/mileage", date=date, type=mileage_type,
146
+ verification_url=verification_url)
147
+
148
+ def vehicle_rates(
149
+ self, rate_type: str, sin: str, *, year: Optional[int] = None,
150
+ verification_url: bool = False,
151
+ ) -> Any:
152
+ return self._get(f"/gsa/vehicle-rates/{segment(rate_type)}/{segment(sin)}", year=year,
153
+ verification_url=verification_url)
@@ -0,0 +1,223 @@
1
+ Metadata-Version: 2.4
2
+ Name: allowances-api
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Allowances API
5
+ Project-URL: Homepage, https://www.allowancesapi.com
6
+ Project-URL: Documentation, https://www.allowancesapi.com/docs
7
+ Project-URL: Repository, https://github.com/allowances-api/allowances-api-python
8
+ Project-URL: Issues, https://github.com/allowances-api/allowances-api-python/issues
9
+ Author-email: Allowances API <contact@allowancesapi.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: allowances,dssr,dtmo,gsa,per-diem,travel
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
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: Typing :: Typed
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: httpx<1,>=0.25
25
+ Provides-Extra: dev
26
+ Requires-Dist: build>=1.2; extra == 'dev'
27
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
28
+ Requires-Dist: pytest>=8; extra == 'dev'
29
+ Requires-Dist: ruff>=0.9; extra == 'dev'
30
+ Requires-Dist: twine>=6; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Allowances API Python SDK
34
+
35
+ The official Python client for [Allowances API](https://www.allowancesapi.com), a normalized API for U.S. government travel and overseas compensation data.
36
+
37
+ The API brings together rates published by three different government sources:
38
+
39
+ - **DSSR** — overseas post allowance (COLA), hardship differential, danger pay, education allowance, living quarters allowance, and foreign per diem.
40
+ - **DTMO** — military per diem for CONUS and OCONUS locations.
41
+ - **GSA** — civilian CONUS per diem, mileage reimbursement, and Fleet vehicle leasing rates.
42
+
43
+ This SDK handles API-key authentication, URL construction, parameter serialization, response decoding, and useful Python exceptions. It is useful for travel platforms, expense systems, payroll and mobility tools, government contractors, and internal applications that need current or historical allowance data without maintaining scrapers for several unrelated sources.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install allowances-api
49
+ ```
50
+
51
+ Python 3.9 or newer is required.
52
+
53
+ ## Authentication
54
+
55
+ Create an API key at [allowancesapi.com](https://www.allowancesapi.com), then provide it directly:
56
+
57
+ ```python
58
+ from allowances_api import AllowancesClient
59
+
60
+ client = AllowancesClient("ak_live_your_key")
61
+ ```
62
+
63
+ For applications, keeping the key in an environment variable is preferable. The client will find your api key if you set it explicitely as ALLOWANCES_API_KEY.
64
+
65
+ ```bash
66
+ export ALLOWANCES_API_KEY="ak_live_your_key"
67
+ ```
68
+
69
+ ```python
70
+ from allowances_api import AllowancesClient
71
+
72
+ client = AllowancesClient() # reads ALLOWANCES_API_KEY
73
+ ```
74
+
75
+ Every request sends the key in the API's `X-API-Key` header. Do not embed a secret API key in browser or mobile client code.
76
+
77
+ ## Quick start
78
+
79
+ Use the client as a context manager so its connection pool is closed automatically:
80
+
81
+ ```python
82
+ from datetime import date
83
+ from allowances_api import AllowancesClient
84
+
85
+ with AllowancesClient() as client:
86
+ # Overseas allowances for Nairobi
87
+ kenya = client.dssr.allowances_by_location(
88
+ "KE",
89
+ "Nairobi",
90
+ date=date.today(),
91
+ type=["cola", "hardship", "danger_pay"],
92
+ )
93
+
94
+ # Military OCONUS per diem
95
+ australia = client.dtmo.perdiem_oconus("AU", query="Adelaide")
96
+
97
+ # Civilian CONUS per diem
98
+ albany = client.gsa.perdiem_by_state("NY", location="Albany")
99
+ ```
100
+
101
+ Responses are returned as decoded dictionaries or lists, matching the documented JSON response exactly.
102
+
103
+ ## Common use cases
104
+
105
+ ### Look up an overseas post allowance and calculate COLA
106
+
107
+ ```python
108
+ rates = client.dssr.allowances_by_postcode("BE", "10112", types=["cola"])
109
+
110
+ estimate = client.dssr.calculate_cola(
111
+ "10112",
112
+ family_size=4,
113
+ base_salary=100_000,
114
+ )
115
+ print(estimate["annual_cola"], estimate["biweekly_cola"])
116
+ ```
117
+
118
+ ### Retrieve per diem by source
119
+
120
+ ```python
121
+ # Department of State foreign per diem
122
+ dssr = client.dssr.perdiem_by_location("KE", "Nairobi")
123
+
124
+ # Defense Travel Management Office military rates
125
+ dtmo = client.dtmo.perdiem_conus("US-CO", query="Aspen")
126
+
127
+ # General Services Administration civilian rates
128
+ gsa = client.gsa.perdiem_by_zipcode("12207")
129
+ ```
130
+
131
+ ### Mileage and GSA Fleet rates
132
+
133
+ ```python
134
+ mileage = client.gsa.mileage(mileage_type="automobile")
135
+ fleet = client.gsa.vehicle_rates("standard", "10B", year=2026)
136
+ ```
137
+
138
+ ### Browse locations
139
+
140
+ ```python
141
+ countries = client.dssr.countries(query="United")
142
+ dssr_locations = client.dssr.country_locations("DE")
143
+ dtmo_locations = client.dtmo.locations_oconus("AU", location="Sydney")
144
+ states = client.gsa.states()
145
+ destinations = client.gsa.destinations(limit=50)
146
+ ```
147
+
148
+ ## Errors
149
+
150
+ The SDK maps common status codes to specific exceptions:
151
+
152
+ ```python
153
+ from allowances_api import ForbiddenError, NotFoundError, RateLimitError
154
+
155
+ try:
156
+ result = client.dssr.perdiem("XX")
157
+ except NotFoundError as exc:
158
+ print(exc.status_code, exc.message, exc.request_id)
159
+ except ForbiddenError:
160
+ print("This request is not included in the current plan")
161
+ except RateLimitError:
162
+ print("Request limit reached")
163
+ ```
164
+
165
+ Available exception classes are `AuthenticationError`, `ForbiddenError`, `NotFoundError`, `ValidationError`, `RateLimitError`, `TransportError`, and the base `AllowancesAPIError`.
166
+
167
+ ## Configuration
168
+
169
+ Use a different endpoint or timeout for local development and testing:
170
+
171
+ ```python
172
+ client = AllowancesClient(
173
+ "test-key",
174
+ base_url="http://localhost:8000/v1",
175
+ timeout=10,
176
+ )
177
+ ```
178
+
179
+ An existing `httpx.Client` can be injected for custom transports, proxies, telemetry, or testing:
180
+
181
+ ```python
182
+ import httpx
183
+ from allowances_api import AllowancesClient
184
+
185
+ transport = httpx.HTTPTransport(retries=2)
186
+ http = httpx.Client(base_url="https://api.allowancesapi.com/v1", transport=transport)
187
+ client = AllowancesClient("ak_live_your_key", http_client=http)
188
+ ```
189
+
190
+ When injecting a client, its lifecycle remains owned by the caller.
191
+
192
+ ## Endpoint mapping
193
+
194
+ SDK methods follow the API's three source groups:
195
+
196
+ - `client.dssr`: allowances, per diem, COLA calculator, countries, and locations.
197
+ - `client.dtmo`: CONUS/OCONUS per diem, search, and locations.
198
+ - `client.gsa`: destination/state/ZIP per diem, ZIP and county metadata, mileage, and vehicle rates.
199
+
200
+ See the complete [API documentation](https://www.allowancesapi.com/docs) for response fields, plan limits, and data-source details.
201
+
202
+ ## Development and publishing
203
+
204
+ ```bash
205
+ python -m venv .venv
206
+ source .venv/bin/activate
207
+ pip install -e '.[dev]'
208
+ pytest
209
+ ruff check .
210
+ python -m build
211
+ twine check dist/*
212
+ ```
213
+
214
+ Publish a release after updating the version in `pyproject.toml` and `src/allowances_api/__init__.py`:
215
+
216
+ ```bash
217
+ twine upload dist/*
218
+ ```
219
+
220
+ ## License
221
+
222
+ MIT
223
+
@@ -0,0 +1,10 @@
1
+ allowances_api/__init__.py,sha256=_U5ICCLtZINjCh7fmxIlYYtmJPn3W9z6y1-8OpEufHA,482
2
+ allowances_api/_utils.py,sha256=nEspXOgbEhU8m_pqRqll58qCmRhtbuhpaIGJu1Snkh8,717
3
+ allowances_api/client.py,sha256=4Hp2kEL31BY3FiGBNUy_yIjqdzuP3tvyVo_GstwoWkc,2936
4
+ allowances_api/exceptions.py,sha256=w7PF5iillU6aAE7qBkjuWOqQJhOSlBOdPYgSpFoN4fg,1781
5
+ allowances_api/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
6
+ allowances_api/resources.py,sha256=tMPI4qLL-DvdZNEYK5EDAhtVbjpM-HZRtbBUzZEY6rw,6730
7
+ allowances_api-0.1.0.dist-info/METADATA,sha256=XGfTCoVhzKjPQMI6cTMbT0tIZkVK1b0kGe53AvXUjas,6943
8
+ allowances_api-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
9
+ allowances_api-0.1.0.dist-info/licenses/LICENSE,sha256=Yk7ewbhJM5FCcIL7KaoBH5Oi2uz5LRgGUTaVlhYSONU,1072
10
+ allowances_api-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Allowances API
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.
22
+