binaaz-python-sdk 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,205 @@
1
+ Metadata-Version: 2.5
2
+ Name: binaaz-python-sdk
3
+ Version: 0.1.0
4
+ Summary: Typed Python client for bina.az: sign in by email or SMS code, post, edit, prolong and delete real estate listings, upload photos, read the catalog. Sync and async.
5
+ Project-URL: Homepage, https://theqiyas.cc/projects/binaaz-sdk/
6
+ Project-URL: Source, https://github.com/qiyascc/binaaz-python-sdk
7
+ Project-URL: Author, https://theqiyas.cc
8
+ Project-URL: Issues, https://github.com/qiyascc/binaaz-python-sdk/issues
9
+ Project-URL: Changelog, https://github.com/qiyascc/binaaz-python-sdk/blob/main/CHANGELOG.md
10
+ Author-email: qiyascc <qiyasturk@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: api,azerbaijan,bina.az,graphql,listings,real estate,sdk
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
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 :: Office/Business
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: goodconf>=7.1
26
+ Requires-Dist: httpx<1,>=0.24
27
+ Requires-Dist: pydantic>=2.7
28
+ Requires-Dist: pyyaml>=6
29
+ Provides-Extra: dev
30
+ Requires-Dist: goodconf[toml]>=7.1; extra == 'dev'
31
+ Requires-Dist: goodconf[yaml]>=7.1; extra == 'dev'
32
+ Requires-Dist: mypy>=1.8; extra == 'dev'
33
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
34
+ Requires-Dist: pytest>=7; extra == 'dev'
35
+ Requires-Dist: ruff>=0.4; extra == 'dev'
36
+ Requires-Dist: types-pyyaml; extra == 'dev'
37
+ Provides-Extra: toml
38
+ Requires-Dist: goodconf[toml]>=7.1; extra == 'toml'
39
+ Provides-Extra: yaml
40
+ Requires-Dist: goodconf[yaml]>=7.1; extra == 'yaml'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # binaaz-python-sdk
44
+
45
+ [![Python](https://img.shields.io/badge/python-3.10%20to%203.13-3776AB?logo=python&logoColor=white)](https://www.python.org)
46
+ [![bina.az](https://img.shields.io/badge/site-bina.az-E4402F)](https://bina.az)
47
+ [![Typed](https://img.shields.io/badge/typing-mypy-2A6DB2)](https://mypy-lang.org)
48
+ [![Async](https://img.shields.io/badge/sync%20%2B%20async-httpx-5A45FF)](https://www.python-httpx.org)
49
+ [![Ruff](https://img.shields.io/badge/lint-ruff-D7FF64?logo=ruff&logoColor=black)](https://docs.astral.sh/ruff)
50
+ [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/qiyascc/binaaz-python-sdk/blob/main/LICENSE)
51
+
52
+ Typed Python client for bina.az, the Azerbaijani real estate site. Sign in with email and password or with an SMS code, post, edit, prolong and delete listings, upload and rotate photos, read the catalog of cities, districts and metro stations. Sync and async, with retry rules built so a listing or a payment is never sent twice.
53
+
54
+ ```bash
55
+ pip install binaaz-python-sdk
56
+ ```
57
+
58
+ This is not an official bina.az product. It talks to the same endpoints the bina.az website uses, with its own User-Agent. It does not solve captchas or Cloudflare challenges: if bina.az answers with a challenge page you get `BlockedError`.
59
+
60
+ ## Sign in
61
+
62
+ ```python
63
+ from binaaz_sdk import BinaAz
64
+
65
+ with BinaAz() as bina:
66
+ bina.auth.login("agency@example.az", "password")
67
+ saved = bina.cookie
68
+ ```
69
+
70
+ Phone accounts sign in with a code that bina.az sends by SMS:
71
+
72
+ ```python
73
+ sent = bina.auth.send_code("050 123 45 67")
74
+ profile = bina.auth.verify_code("050 123 45 67", "1234")
75
+ ```
76
+
77
+ `bina.cookie` is the signed in session. Keep it somewhere safe and pass it back later, so you do not sign in on every run:
78
+
79
+ ```python
80
+ bina = BinaAz(cookie=saved)
81
+ bina.profile()
82
+ ```
83
+
84
+ A cookie copied from a browser works too, as a header value, a `{"name": "value"}` mapping or a cookie export list. Cloudflare cookies are dropped, they are never stored or sent. Cookies that bina.az renews are picked up from every answer.
85
+
86
+ ## Post a listing
87
+
88
+ ```python
89
+ from binaaz_sdk import BinaAz, Category, ContactType, Listing
90
+
91
+ photos = bina.photos.upload_many(["1.jpg", "2.jpg", "3.jpg", "4.jpg"])
92
+
93
+ result = bina.items.create(Listing(
94
+ category_id=Category.APARTMENT_OLD_BUILDING,
95
+ is_leased=False,
96
+ price=120000,
97
+ rooms=3,
98
+ area={"value": 85},
99
+ floor={"value": 4, "total": 9},
100
+ location={"address": "Nizami street 10", "city_id": "1", "latitude": 40.379, "longitude": 49.846},
101
+ contact={"type": ContactType.AGENT, "name": "Agency"},
102
+ photo_ids=[p.id for p in photos],
103
+ description="Renovated three room flat near the metro.",
104
+ ))
105
+ result.id, result.url, result.state
106
+ ```
107
+
108
+ bina.az takes 4 to 30 photos on a listing. A new listing starts as `pending` and moves to `published`, `rejected` or `awaiting_payment`. When bina.az refuses it, `ItemError.status` tells why: `RESTRICTED` (confirm the phone number first), `UNVERIFIED`, `INVALID` or `FAILURE`. Field problems are in `ItemError.errors`.
109
+
110
+ ## Manage listings
111
+
112
+ ```python
113
+ page = bina.items.list("published", first=50)
114
+ everything = bina.items.all()
115
+ item = bina.items.get("4123456")
116
+
117
+ bina.items.update("4123456", listing)
118
+ bina.items.prolong("4123456")
119
+ bina.items.delete("4123456")
120
+ ```
121
+
122
+ `list` follows bina.az cursors (`page.next_cursor`), `all` reads every page. Tabs are `pending`, `published`, `rejected`, `expired` and `awaiting_payment`. An update of a listing under moderation raises `ItemError` with `status="LOCKED"` and `locked_until`. Listing management needs a company profile (agencies and residences); other profiles get `NotSupportedError`.
123
+
124
+ ## Paid services
125
+
126
+ ```python
127
+ for service in bina.items.paid_services("4123456"):
128
+ service.title, service.options, service.payment_methods
129
+
130
+ payment = bina.items.buy_service("4123456", service.id, "wallet", option_id=service.options[0].id, confirm=True)
131
+ payment.redirect_url
132
+ bina.items.pay("4123456", confirm=True)
133
+ ```
134
+
135
+ Anything that costs money needs `confirm=True`, otherwise `InvalidRequestError` is raised before a request is made. A card payment comes back with `redirect_url`, the page where the payment is finished.
136
+
137
+ ## Photos and catalog
138
+
139
+ ```python
140
+ photo = bina.photos.upload(open("cover.jpg", "rb"))
141
+ photo = bina.photos.rotate(photo.id, 90)
142
+
143
+ bina.catalog.categories()
144
+ bina.catalog.cities()
145
+ bina.catalog.regions("1")
146
+ bina.catalog.townships("4")
147
+ bina.catalog.landmarks("1", 40.379, 49.846)
148
+ ```
149
+
150
+ Uploads take bytes, a path or a file object. Rotating returns a new photo id; use that one in the listing. Catalog calls need no session.
151
+
152
+ ## Safety
153
+
154
+ | Situation | What the client does |
155
+ | --- | --- |
156
+ | Connection refused, nothing sent | Retries, then `NotSentError` |
157
+ | Timeout, cut connection or 5xx on create, update, delete, prolong, payment, sign in or photo upload | No retry, `OutcomeUnknownError`. Check the account before trying again |
158
+ | Same failure on a read | Retries with backoff, then `TransportError` or `ServerError` |
159
+ | 429 | Waits `Retry-After` and retries, the request was not processed |
160
+ | SMS login service answers with an error page instead of a known error | Retried up to `code_attempts` (3), the login service drops some answers |
161
+ | SMS code sent only on a retry | `CodeSent.retried` is `True`: more than one SMS may arrive, use the latest code |
162
+ | SMS code rejected after a retry | `CodeRejectedError` says the code may have been used by the earlier attempt |
163
+ | Timeout on the SMS login service | Not resent, `OutcomeUnknownError` |
164
+ | Cloudflare challenge page | `BlockedError`, never solved or retried |
165
+ | Paid service or paying for a listing without `confirm=True` | `InvalidRequestError`, nothing is sent |
166
+ | Fewer than 4 or more than 30 photos | `InvalidRequestError` before anything is sent |
167
+ | A failed sign in | The previous session is kept |
168
+ | Plain http address outside localhost | `ConfigurationError`, unless `allow_insecure_http` |
169
+
170
+ ## Endpoints
171
+
172
+ | Namespace | Methods |
173
+ | --- | --- |
174
+ | `auth` | `login` `send_code` `verify_code` `profile` |
175
+ | `items` | `create` `update` `delete` `prolong` `get` `list` `all` `paid_services` `buy_service` `pay` |
176
+ | `photos` | `upload` `upload_many` `rotate` |
177
+ | `catalog` | `categories` `cities` `regions` `townships` `landmarks` |
178
+ | client | `profile` `cookie` `set_cookie` `clear_session` `graphql` |
179
+
180
+ `AsyncBinaAz` has the same methods, awaited. `graphql(query, variables)` sends any other query with the session.
181
+
182
+ ## Errors
183
+
184
+ `BinaAzError` is the base. Answers raise `APIError` subclasses: `BadRequestError`, `AuthenticationError` (the session is not signed in), `NotFoundError`, `RateLimitError`, `ServerError` and `BlockedError`. GraphQL errors raise `GraphQLError` with `errors`, refused listings raise `ItemError` with `status`, `errors` and `locked_until`, a wrong SMS code raises `CodeRejectedError`. Each carries `status_code`, `message` and the raw `body`.
185
+
186
+ ## Config
187
+
188
+ Pass values in code, or set `BINAAZ_COOKIE`, `BINAAZ_TIMEOUT`, `BINAAZ_MAX_RETRIES`, `BINAAZ_CODE_ATTEMPTS`, `BINAAZ_USER_AGENT` and the rest, or put them in `binaaz.toml` ([example](https://github.com/qiyascc/binaaz-python-sdk/blob/main/examples/binaaz.toml)). Every path is a setting (`BINAAZ_ENDPOINTS__GRAPHQL=...`). Messages live in `messages.yaml` and can be replaced with `messages.use(...)`.
189
+
190
+ ## Develop
191
+
192
+ ```bash
193
+ pip install -e ".[dev]" && pytest && ruff check . && mypy src
194
+ ```
195
+
196
+ ## Links
197
+
198
+ - Source code: [github.com/qiyascc/binaaz-python-sdk](https://github.com/qiyascc/binaaz-python-sdk)
199
+ - PyPI: [pypi.org/project/binaaz-python-sdk](https://pypi.org/project/binaaz-python-sdk/)
200
+ - Project page: [theqiyas.cc/projects/binaaz-sdk](https://theqiyas.cc/projects/binaaz-sdk/)
201
+ - Author: [Qiyas Sadıqov](https://theqiyas.cc), more projects at [theqiyas.cc](https://theqiyas.cc)
202
+
203
+ ## License
204
+
205
+ MIT
@@ -0,0 +1,17 @@
1
+ binaaz_sdk/__init__.py,sha256=gJ5eicuhfpLvEXqeMJNlliomgoiAE_caUGUHnh1yXB4,1841
2
+ binaaz_sdk/_core.py,sha256=MVthr6EaWQ4y76PaaUo0nDzDYWOAYdBaiJmMJbqn4Vo,16648
3
+ binaaz_sdk/_version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
4
+ binaaz_sdk/client.py,sha256=uKIdwHXsrMjrBp1369VrBsH3m1AptvOVBC9egYXRgm0,1754
5
+ binaaz_sdk/config.py,sha256=VvzN5Ioir1C8o0HNAsVlfVfZSDB8XQCi2FWA-UPNAOQ,3998
6
+ binaaz_sdk/enums.py,sha256=n33q-k0K2O2TY7nnDin5K_CjPDr2yJnzndmIEnFY_y8,1588
7
+ binaaz_sdk/errors.py,sha256=SaiT0LFwv8noW5HQZXZLsy9p-DeHA5Kl6_DIKQ61x6g,2586
8
+ binaaz_sdk/messages.py,sha256=GqqTi5PvVlt2G-F6llTb3_gMTlRgVj4DHMUXZQzr7ZY,1601
9
+ binaaz_sdk/messages.yaml,sha256=KpUiVnBlCQZcCq_iEQ786MmYmmrzPdr7ZlO2FpkJKSE,4019
10
+ binaaz_sdk/models.py,sha256=3U5oOMKj2ylzKu70VscI6G6f4QjeeHGO6S1wywM9k-U,11322
11
+ binaaz_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ binaaz_sdk/queries.py,sha256=QEF00I03yHDBFTX8_P9So3fbx6yDK67sbEAjf5MqACQ,2800
13
+ binaaz_sdk/resources.py,sha256=1D6gV1qRMfXY9kbm0-B9b1oyZYjAdU9lS7QWzRVc3kc,19760
14
+ binaaz_python_sdk-0.1.0.dist-info/METADATA,sha256=nKfEmxJENwOiPBtuBqIfv2wKdzqTqD8b8rFJenIL7_E,9621
15
+ binaaz_python_sdk-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
16
+ binaaz_python_sdk-0.1.0.dist-info/licenses/LICENSE,sha256=ENVL-_Eh4n1SumZuLuTjO2K0OOL_eXh4pkeme35b02I,1064
17
+ binaaz_python_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 qiyascc
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.
binaaz_sdk/__init__.py ADDED
@@ -0,0 +1,97 @@
1
+ from . import messages
2
+ from ._version import __version__
3
+ from .client import AsyncBinaAz, BinaAz
4
+ from .config import Endpoints, Settings, load_settings
5
+ from .enums import PHOTO_ANGLES, PHOTOS_MAX, PHOTOS_MIN, BuildingType, Category, ContactType, ItemState, ItemTab
6
+ from .errors import (
7
+ APIError,
8
+ AuthenticationError,
9
+ BadRequestError,
10
+ BinaAzError,
11
+ BlockedError,
12
+ CodeRejectedError,
13
+ ConfigurationError,
14
+ GraphQLError,
15
+ InvalidRequestError,
16
+ ItemError,
17
+ NotFoundError,
18
+ NotSentError,
19
+ NotSupportedError,
20
+ OutcomeUnknownError,
21
+ RateLimitError,
22
+ ServerError,
23
+ TransportError,
24
+ )
25
+ from .models import (
26
+ Area,
27
+ CategoryNode,
28
+ CodeSent,
29
+ Contact,
30
+ Floor,
31
+ Item,
32
+ ItemPage,
33
+ ItemResult,
34
+ Listing,
35
+ Location,
36
+ PaidService,
37
+ Payment,
38
+ PaymentMethod,
39
+ Photo,
40
+ Place,
41
+ Price,
42
+ Profile,
43
+ ServiceOption,
44
+ )
45
+
46
+ __all__ = [
47
+ "__version__",
48
+ "BinaAz",
49
+ "AsyncBinaAz",
50
+ "Settings",
51
+ "Endpoints",
52
+ "load_settings",
53
+ "messages",
54
+ "Category",
55
+ "ContactType",
56
+ "BuildingType",
57
+ "ItemState",
58
+ "ItemTab",
59
+ "PHOTO_ANGLES",
60
+ "PHOTOS_MIN",
61
+ "PHOTOS_MAX",
62
+ "Listing",
63
+ "Area",
64
+ "Floor",
65
+ "Location",
66
+ "Contact",
67
+ "Profile",
68
+ "Item",
69
+ "ItemPage",
70
+ "ItemResult",
71
+ "Price",
72
+ "Photo",
73
+ "Place",
74
+ "CategoryNode",
75
+ "PaidService",
76
+ "ServiceOption",
77
+ "PaymentMethod",
78
+ "Payment",
79
+ "CodeSent",
80
+ "BinaAzError",
81
+ "ConfigurationError",
82
+ "InvalidRequestError",
83
+ "TransportError",
84
+ "NotSentError",
85
+ "OutcomeUnknownError",
86
+ "APIError",
87
+ "BlockedError",
88
+ "BadRequestError",
89
+ "AuthenticationError",
90
+ "NotFoundError",
91
+ "RateLimitError",
92
+ "ServerError",
93
+ "GraphQLError",
94
+ "ItemError",
95
+ "NotSupportedError",
96
+ "CodeRejectedError",
97
+ ]