listingsapi 0.5.2__tar.gz → 0.5.4__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.
- {listingsapi-0.5.2/src/listingsapi.egg-info → listingsapi-0.5.4}/PKG-INFO +3 -3
- {listingsapi-0.5.2 → listingsapi-0.5.4}/README.md +2 -2
- {listingsapi-0.5.2 → listingsapi-0.5.4}/pyproject.toml +1 -1
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/__init__.py +1 -1
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/_client.py +5 -1
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/resources/connected_accounts.py +1 -1
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/resources/locations.py +4 -2
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/resources/photos.py +1 -1
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/resources/posts.py +20 -14
- {listingsapi-0.5.2 → listingsapi-0.5.4/src/listingsapi.egg-info}/PKG-INFO +3 -3
- {listingsapi-0.5.2 → listingsapi-0.5.4}/tests/test_listingsapi.py +88 -13
- {listingsapi-0.5.2 → listingsapi-0.5.4}/LICENSE +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/setup.cfg +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/_types.py +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/_utils.py +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/exceptions.py +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/py.typed +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/resources/__init__.py +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/resources/_base.py +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/resources/analytics.py +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/resources/listings.py +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/resources/reviews.py +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi/resources/workflows.py +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi.egg-info/SOURCES.txt +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi.egg-info/dependency_links.txt +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi.egg-info/requires.txt +0 -0
- {listingsapi-0.5.2 → listingsapi-0.5.4}/src/listingsapi.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: listingsapi
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: Python SDK for listingsAPI — listings, reviews, posts, and analytics for local marketing
|
|
5
5
|
Author-email: listingsAPI <support@listingsapi.com>
|
|
6
6
|
License: MIT License
|
|
@@ -355,7 +355,7 @@ client.connected_accounts.oauth_disconnect(16808, "FACEBOOK")
|
|
|
355
355
|
|
|
356
356
|
```python
|
|
357
357
|
sites = client.plan_sites() # directories included in your plan
|
|
358
|
-
countries = client.countries() # supported countries and
|
|
358
|
+
countries = client.countries() # supported countries, states, and address rules
|
|
359
359
|
subcategories = client.subcategories() # business categories (IDs for locations.add)
|
|
360
360
|
subs = client.subscriptions() # active subscriptions
|
|
361
361
|
```
|
|
@@ -389,7 +389,7 @@ print(f"Score: {audit.health_score}%, Issues: {audit.issue_count}")
|
|
|
389
389
|
| Method | Description |
|
|
390
390
|
|--------|-------------|
|
|
391
391
|
| `client.plan_sites()` | Directories included in your plan |
|
|
392
|
-
| `client.countries()` | Supported countries and states (ISO codes) |
|
|
392
|
+
| `client.countries()` | Supported countries and states (ISO codes), with `hasCity`, `stateRequired` and `hasPostalCode` for each country |
|
|
393
393
|
| `client.subcategories()` | Business categories (IDs for `locations.add`) |
|
|
394
394
|
| `client.subscriptions()` | Active subscriptions for the account |
|
|
395
395
|
|
|
@@ -303,7 +303,7 @@ client.connected_accounts.oauth_disconnect(16808, "FACEBOOK")
|
|
|
303
303
|
|
|
304
304
|
```python
|
|
305
305
|
sites = client.plan_sites() # directories included in your plan
|
|
306
|
-
countries = client.countries() # supported countries and
|
|
306
|
+
countries = client.countries() # supported countries, states, and address rules
|
|
307
307
|
subcategories = client.subcategories() # business categories (IDs for locations.add)
|
|
308
308
|
subs = client.subscriptions() # active subscriptions
|
|
309
309
|
```
|
|
@@ -337,7 +337,7 @@ print(f"Score: {audit.health_score}%, Issues: {audit.issue_count}")
|
|
|
337
337
|
| Method | Description |
|
|
338
338
|
|--------|-------------|
|
|
339
339
|
| `client.plan_sites()` | Directories included in your plan |
|
|
340
|
-
| `client.countries()` | Supported countries and states (ISO codes) |
|
|
340
|
+
| `client.countries()` | Supported countries and states (ISO codes), with `hasCity`, `stateRequired` and `hasPostalCode` for each country |
|
|
341
341
|
| `client.subcategories()` | Business categories (IDs for `locations.add`) |
|
|
342
342
|
| `client.subscriptions()` | Active subscriptions for the account |
|
|
343
343
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "listingsapi"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.4"
|
|
8
8
|
description = "Python SDK for listingsAPI — listings, reviews, posts, and analytics for local marketing"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -263,7 +263,11 @@ class ListingsAPI:
|
|
|
263
263
|
return [APIObject(item) for item in items]
|
|
264
264
|
|
|
265
265
|
def countries(self) -> list[APIObject]:
|
|
266
|
-
"""Get supported countries and states (ISO codes).
|
|
266
|
+
"""Get supported countries and states (ISO codes).
|
|
267
|
+
|
|
268
|
+
Each country has ``hasCity``, ``stateRequired`` and ``hasPostalCode``,
|
|
269
|
+
which tell you which address fields a location in that country needs.
|
|
270
|
+
"""
|
|
267
271
|
data = self._get("countries")
|
|
268
272
|
items = data.get("data", {}).get("supportedCountries") or []
|
|
269
273
|
return [APIObject(item) for item in items]
|
|
@@ -147,7 +147,7 @@ class ConnectedAccounts(APIResource):
|
|
|
147
147
|
}
|
|
148
148
|
},
|
|
149
149
|
)
|
|
150
|
-
return APIObject(data.get("data", {}).get("
|
|
150
|
+
return APIObject(data.get("data", {}).get("createConnectUrl") or {})
|
|
151
151
|
|
|
152
152
|
def oauth_disconnect(self, location_id: str | int, site: str) -> APIObject:
|
|
153
153
|
"""Disconnect a Google/Facebook profile from a single location."""
|
|
@@ -191,8 +191,10 @@ class Locations(APIResource):
|
|
|
191
191
|
city: City. Required for countries with city-level addressing; may be
|
|
192
192
|
omitted only for service-area businesses with hide_address=True.
|
|
193
193
|
street: Street address.
|
|
194
|
-
state_iso: State/region ISO code.
|
|
195
|
-
|
|
194
|
+
state_iso: State/region ISO code. Required when the country's
|
|
195
|
+
``stateRequired`` is true (see ``client.countries()``).
|
|
196
|
+
postal_code: Postal or ZIP code. Send it when the country's
|
|
197
|
+
``hasPostalCode`` is true.
|
|
196
198
|
phone: Primary phone number, validated for the country.
|
|
197
199
|
website: Business website URL.
|
|
198
200
|
store_id: Your unique store code; must be unique across the account.
|
|
@@ -44,7 +44,7 @@ class Photos(APIResource):
|
|
|
44
44
|
"""Star or unstar photos. Max 4 starred photos per account."""
|
|
45
45
|
data = self._post(
|
|
46
46
|
"locations/photos/star",
|
|
47
|
-
{"input": {"locationId": encode_location_id(location_id), "
|
|
47
|
+
{"input": {"locationId": encode_location_id(location_id), "photoIds": media_ids, "starred": starred}},
|
|
48
48
|
)
|
|
49
49
|
return APIObject(data.get("data", {}).get("starUnstarLocationPhotos") or {})
|
|
50
50
|
|
|
@@ -30,13 +30,14 @@ class Posts(APIResource):
|
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
def create(self, body: dict[str, Any]) -> APIObject:
|
|
33
|
-
"""Create a post from a raw
|
|
33
|
+
"""Create a post from a raw field dict.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
Pass the fields flat — they are wrapped in the ``input`` object the API
|
|
36
|
+
requires before sending. Required: postName, locationIds (base64),
|
|
37
|
+
postType, postSites. Prefer create_announcement / create_event /
|
|
38
|
+
create_offer for keyword arguments and client-side validation.
|
|
38
39
|
"""
|
|
39
|
-
data = self._post("posts", body)
|
|
40
|
+
data = self._post("posts", {"input": body})
|
|
40
41
|
return APIObject(data.get("data", {}).get("createSocialPost") or {})
|
|
41
42
|
|
|
42
43
|
def create_announcement(
|
|
@@ -232,8 +233,8 @@ class Posts(APIResource):
|
|
|
232
233
|
context_info=context_info,
|
|
233
234
|
additional_fields=additional_fields,
|
|
234
235
|
)
|
|
235
|
-
data = self._post("bulk-posts", body)
|
|
236
|
-
return APIObject(data.get("data", {}).get("
|
|
236
|
+
data = self._post("bulk-posts", {"input": body})
|
|
237
|
+
return APIObject(data.get("data", {}).get("createSocialPost") or {})
|
|
237
238
|
|
|
238
239
|
def retrieve(self, post_id: str) -> APIObject:
|
|
239
240
|
"""Get a post with its content, per-site publish status, and analytics."""
|
|
@@ -258,13 +259,16 @@ class Posts(APIResource):
|
|
|
258
259
|
"""List post campaigns targeting a location.
|
|
259
260
|
|
|
260
261
|
Offset-paginated: returns .records plus .pageInfo with totalPages,
|
|
261
|
-
totalRecords, hasNextPage.
|
|
262
|
-
|
|
262
|
+
totalRecords, hasNextPage.
|
|
263
|
+
|
|
264
|
+
Note: this route rejects a tag — the API errors when one is sent — so
|
|
265
|
+
``tag`` is accepted for backwards compatibility and never put on the
|
|
266
|
+
wire. Use bulk_list_for_location when you need tag filtering.
|
|
263
267
|
"""
|
|
264
|
-
params = self._list_params(
|
|
268
|
+
params = self._list_params(None, page, per_page, filters, sort_fields)
|
|
265
269
|
encoded = encode_location_id(location_id)
|
|
266
270
|
data = self._get(f"locations/{encoded}/posts", params)
|
|
267
|
-
return APIObject(data.get("data", {}).get("
|
|
271
|
+
return APIObject(data.get("data", {}).get("postsByLocation") or {})
|
|
268
272
|
|
|
269
273
|
def bulk_retrieve(self, bulk_post_id: str) -> APIObject:
|
|
270
274
|
"""Get a bulk campaign with per-location publish status and analytics."""
|
|
@@ -293,7 +297,7 @@ class Posts(APIResource):
|
|
|
293
297
|
sites=sites if sites is not None else ["GOOGLE"],
|
|
294
298
|
**kwargs,
|
|
295
299
|
)
|
|
296
|
-
data = self._post("posts", body)
|
|
300
|
+
data = self._post("posts", {"input": body})
|
|
297
301
|
return APIObject(data.get("data", {}).get("createSocialPost") or {})
|
|
298
302
|
|
|
299
303
|
def _build_post_body(
|
|
@@ -358,13 +362,15 @@ class Posts(APIResource):
|
|
|
358
362
|
|
|
359
363
|
def _list_params(
|
|
360
364
|
self,
|
|
361
|
-
tag: str,
|
|
365
|
+
tag: str | None,
|
|
362
366
|
page: int | None,
|
|
363
367
|
per_page: int | None,
|
|
364
368
|
filters: dict[str, Any] | None,
|
|
365
369
|
sort_fields: dict[str, Any] | None,
|
|
366
370
|
) -> dict[str, Any]:
|
|
367
|
-
params: dict[str, Any] = {
|
|
371
|
+
params: dict[str, Any] = {}
|
|
372
|
+
if tag is not None:
|
|
373
|
+
params["tag"] = tag
|
|
368
374
|
if page is not None:
|
|
369
375
|
params["page"] = page
|
|
370
376
|
if per_page is not None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: listingsapi
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: Python SDK for listingsAPI — listings, reviews, posts, and analytics for local marketing
|
|
5
5
|
Author-email: listingsAPI <support@listingsapi.com>
|
|
6
6
|
License: MIT License
|
|
@@ -355,7 +355,7 @@ client.connected_accounts.oauth_disconnect(16808, "FACEBOOK")
|
|
|
355
355
|
|
|
356
356
|
```python
|
|
357
357
|
sites = client.plan_sites() # directories included in your plan
|
|
358
|
-
countries = client.countries() # supported countries and
|
|
358
|
+
countries = client.countries() # supported countries, states, and address rules
|
|
359
359
|
subcategories = client.subcategories() # business categories (IDs for locations.add)
|
|
360
360
|
subs = client.subscriptions() # active subscriptions
|
|
361
361
|
```
|
|
@@ -389,7 +389,7 @@ print(f"Score: {audit.health_score}%, Issues: {audit.issue_count}")
|
|
|
389
389
|
| Method | Description |
|
|
390
390
|
|--------|-------------|
|
|
391
391
|
| `client.plan_sites()` | Directories included in your plan |
|
|
392
|
-
| `client.countries()` | Supported countries and states (ISO codes) |
|
|
392
|
+
| `client.countries()` | Supported countries and states (ISO codes), with `hasCity`, `stateRequired` and `hasPostalCode` for each country |
|
|
393
393
|
| `client.subcategories()` | Business categories (IDs for `locations.add`) |
|
|
394
394
|
| `client.subscriptions()` | Active subscriptions for the account |
|
|
395
395
|
|
|
@@ -450,7 +450,7 @@ def test_analytics_google(client):
|
|
|
450
450
|
|
|
451
451
|
|
|
452
452
|
def test_version():
|
|
453
|
-
assert listingsapi.__version__ == "0.5.
|
|
453
|
+
assert listingsapi.__version__ == "0.5.4"
|
|
454
454
|
|
|
455
455
|
|
|
456
456
|
# --- locations.add (one-call create with validation) ---
|
|
@@ -658,7 +658,7 @@ CREATE_POST_OK = {
|
|
|
658
658
|
}
|
|
659
659
|
BULK_POST_OK = {
|
|
660
660
|
"data": {
|
|
661
|
-
"
|
|
661
|
+
"createSocialPost": {
|
|
662
662
|
"success": True,
|
|
663
663
|
"errors": [],
|
|
664
664
|
"socialPost": {"id": "QnVsa1Bvc3Q6OTk=", "status": "INPROGRESS"},
|
|
@@ -667,7 +667,7 @@ BULK_POST_OK = {
|
|
|
667
667
|
}
|
|
668
668
|
|
|
669
669
|
|
|
670
|
-
def
|
|
670
|
+
def test_posts_create_announcement_wraps_body_in_input(client):
|
|
671
671
|
with requests_mock.Mocker() as m:
|
|
672
672
|
m.post("https://listingsapi.com/api/v4/posts", json=CREATE_POST_OK)
|
|
673
673
|
result = client.posts.create_announcement(
|
|
@@ -680,8 +680,9 @@ def test_posts_create_announcement_builds_flat_body(client):
|
|
|
680
680
|
media_url="https://cdn.example.com/opening.jpg",
|
|
681
681
|
)
|
|
682
682
|
assert result.success is True
|
|
683
|
-
|
|
684
|
-
assert "input"
|
|
683
|
+
envelope = m.last_request.json()
|
|
684
|
+
assert list(envelope) == ["input"]
|
|
685
|
+
sent = envelope["input"]
|
|
685
686
|
assert sent["postName"] == "Grand Opening"
|
|
686
687
|
assert sent["postType"] == "ANNOUNCEMENT"
|
|
687
688
|
assert sent["postSites"] == ["GOOGLE"]
|
|
@@ -691,6 +692,34 @@ def test_posts_create_announcement_builds_flat_body(client):
|
|
|
691
692
|
assert sent["postMediaUrl"] == [{"site": "GOOGLE", "url": "https://cdn.example.com/opening.jpg", "type": "IMAGE"}]
|
|
692
693
|
|
|
693
694
|
|
|
695
|
+
def test_posts_create_raw_body_is_input_wrapped(client):
|
|
696
|
+
with requests_mock.Mocker() as m:
|
|
697
|
+
m.post("https://listingsapi.com/api/v4/posts", json=CREATE_POST_OK)
|
|
698
|
+
result = client.posts.create({
|
|
699
|
+
"postName": "Raw",
|
|
700
|
+
"locationIds": ["TG9jYXRpb246MTY4MDg="],
|
|
701
|
+
"postType": "ANNOUNCEMENT",
|
|
702
|
+
"postSites": ["GOOGLE"],
|
|
703
|
+
})
|
|
704
|
+
assert result.success is True
|
|
705
|
+
envelope = m.last_request.json()
|
|
706
|
+
assert list(envelope) == ["input"]
|
|
707
|
+
assert envelope["input"]["postName"] == "Raw"
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def test_posts_bulk_publish_wraps_body_in_input(client):
|
|
711
|
+
with requests_mock.Mocker() as m:
|
|
712
|
+
m.post("https://listingsapi.com/api/v4/bulk-posts", json=BULK_POST_OK)
|
|
713
|
+
result = client.posts.bulk_publish(
|
|
714
|
+
name="Holiday hours", location_ids=[16808], message="Open late!",
|
|
715
|
+
)
|
|
716
|
+
# reads createSocialPost, not createBulkSocialPost
|
|
717
|
+
assert result.socialPost.id == "QnVsa1Bvc3Q6OTk="
|
|
718
|
+
envelope = m.last_request.json()
|
|
719
|
+
assert list(envelope) == ["input"]
|
|
720
|
+
assert envelope["input"]["postName"] == "Holiday hours"
|
|
721
|
+
|
|
722
|
+
|
|
694
723
|
def test_posts_bulk_publish_defaults_to_google_and_facebook(client):
|
|
695
724
|
with requests_mock.Mocker() as m:
|
|
696
725
|
m.post("https://listingsapi.com/api/v4/bulk-posts", json=BULK_POST_OK)
|
|
@@ -700,7 +729,7 @@ def test_posts_bulk_publish_defaults_to_google_and_facebook(client):
|
|
|
700
729
|
message="Open late through the holidays!",
|
|
701
730
|
)
|
|
702
731
|
assert result.success is True
|
|
703
|
-
sent = m.last_request.json()
|
|
732
|
+
sent = m.last_request.json()["input"]
|
|
704
733
|
assert sent["postSites"] == ["GOOGLE", "FACEBOOK"]
|
|
705
734
|
assert len(sent["postMessage"]) == 2
|
|
706
735
|
assert {e["site"] for e in sent["postMessage"]} == {"GOOGLE", "FACEBOOK"}
|
|
@@ -715,7 +744,7 @@ def test_posts_bulk_publish_per_site_messages(client):
|
|
|
715
744
|
location_ids=[16808],
|
|
716
745
|
message={"GOOGLE": "New summer menu!", "FACEBOOK": "Swing by for the summer menu."},
|
|
717
746
|
)
|
|
718
|
-
sent = m.last_request.json()
|
|
747
|
+
sent = m.last_request.json()["input"]
|
|
719
748
|
by_site = {e["site"]: e["message"] for e in sent["postMessage"]}
|
|
720
749
|
assert by_site["GOOGLE"] == "New summer menu!"
|
|
721
750
|
assert by_site["FACEBOOK"] == "Swing by for the summer menu."
|
|
@@ -767,7 +796,7 @@ def test_posts_create_event_context(client):
|
|
|
767
796
|
start_time="7:00pm",
|
|
768
797
|
end_time="10:00pm",
|
|
769
798
|
)
|
|
770
|
-
sent = m.last_request.json()
|
|
799
|
+
sent = m.last_request.json()["input"]
|
|
771
800
|
assert sent["postType"] == "EVENT"
|
|
772
801
|
assert sent["postContextInfo"] == {
|
|
773
802
|
"title": "Jazz Night", "startDay": "2026-08-01", "endDay": "2026-08-01",
|
|
@@ -789,7 +818,7 @@ def test_posts_create_offer_context(client):
|
|
|
789
818
|
start_day="2026-08-01",
|
|
790
819
|
end_day="2026-08-07",
|
|
791
820
|
)
|
|
792
|
-
sent = m.last_request.json()
|
|
821
|
+
sent = m.last_request.json()["input"]
|
|
793
822
|
assert sent["postType"] == "OFFER"
|
|
794
823
|
assert sent["postContextInfo"]["couponCode"] == "SUMMER20"
|
|
795
824
|
assert sent["postContextInfo"]["title"] == "Summer Sale"
|
|
@@ -808,17 +837,35 @@ def test_posts_retrieve_and_bulk_retrieve(client):
|
|
|
808
837
|
assert bulk.socialPostId == "QnVsa1Bvc3Q6OTk="
|
|
809
838
|
|
|
810
839
|
|
|
811
|
-
def
|
|
812
|
-
response = {"data": {"
|
|
840
|
+
def test_posts_list_for_location_sends_no_tag_and_reads_posts_by_location(client):
|
|
841
|
+
response = {"data": {"postsByLocation": {"records": [{"id": "p1"}], "pageInfo": {"totalPages": 1, "totalRecords": 1, "hasNextPage": False}}}}
|
|
813
842
|
with requests_mock.Mocker() as m:
|
|
814
843
|
m.get("https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTY4MDg=/posts", json=response)
|
|
815
844
|
result = client.posts.list_for_location(16808, page=1, per_page=10)
|
|
816
845
|
assert result.records[0].id == "p1"
|
|
817
846
|
assert result.pageInfo.totalRecords == 1
|
|
818
|
-
|
|
847
|
+
# the route raises when a tag is supplied, so none is sent
|
|
848
|
+
assert "tag" not in m.last_request.qs
|
|
819
849
|
assert m.last_request.qs["page"] == ["1"]
|
|
820
850
|
|
|
821
851
|
|
|
852
|
+
def test_posts_list_for_location_ignores_tag_argument(client):
|
|
853
|
+
response = {"data": {"postsByLocation": {"records": []}}}
|
|
854
|
+
with requests_mock.Mocker() as m:
|
|
855
|
+
m.get("https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTY4MDg=/posts", json=response)
|
|
856
|
+
client.posts.list_for_location(16808, tag="all")
|
|
857
|
+
assert "tag" not in m.last_request.qs
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
def test_posts_bulk_list_for_location_still_sends_tag(client):
|
|
861
|
+
response = {"data": {"rollupSocialPosts": {"records": [{"id": "b1"}]}}}
|
|
862
|
+
with requests_mock.Mocker() as m:
|
|
863
|
+
m.get("https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTY4MDg=/bulk-posts", json=response)
|
|
864
|
+
result = client.posts.bulk_list_for_location(16808)
|
|
865
|
+
assert result.records[0].id == "b1"
|
|
866
|
+
assert m.last_request.qs["tag"] == ["all"]
|
|
867
|
+
|
|
868
|
+
|
|
822
869
|
def test_posts_delete(client):
|
|
823
870
|
with requests_mock.Mocker() as m:
|
|
824
871
|
m.delete("https://listingsapi.com/api/v4/posts/U29jaWFsUG9zdDo0NDEyMg==",
|
|
@@ -828,7 +875,7 @@ def test_posts_delete(client):
|
|
|
828
875
|
|
|
829
876
|
|
|
830
877
|
def test_posts_bulk_publish_api_failure_raises(client):
|
|
831
|
-
body = {"data": {"
|
|
878
|
+
body = {"data": {"createSocialPost": {"success": False, "errors": [{"code": "SY20001", "message": "Image URL unreachable"}]}}}
|
|
832
879
|
with requests_mock.Mocker() as m:
|
|
833
880
|
m.post("https://listingsapi.com/api/v4/bulk-posts", json=body)
|
|
834
881
|
with pytest.raises(ValidationError) as exc:
|
|
@@ -955,3 +1002,31 @@ def test_post_without_key_sends_no_idempotency_header(client):
|
|
|
955
1002
|
m.post("https://listingsapi.com/api/v4/locations", json={"data": {}})
|
|
956
1003
|
client._post("locations", {"input": {"name": "Acme"}})
|
|
957
1004
|
assert "Idempotency-Key" not in m.last_request.headers
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
# --- Wire-shape fixes swept in 0.5.3 ---
|
|
1008
|
+
|
|
1009
|
+
def test_photos_star_sends_photo_ids(client):
|
|
1010
|
+
response = {"data": {"starUnstarLocationPhotos": {"success": True}}}
|
|
1011
|
+
with requests_mock.Mocker() as m:
|
|
1012
|
+
m.post("https://listingsapi.com/api/v4/locations/photos/star", json=response)
|
|
1013
|
+
result = client.photos.star(16808, ["TWVkaWFGaWxlOjE="])
|
|
1014
|
+
assert result.success is True
|
|
1015
|
+
sent = m.last_request.json()["input"]
|
|
1016
|
+
assert sent["photoIds"] == ["TWVkaWFGaWxlOjE="]
|
|
1017
|
+
assert "mediaIds" not in sent
|
|
1018
|
+
assert sent["starred"] is True
|
|
1019
|
+
assert sent["locationId"] == "TG9jYXRpb246MTY4MDg="
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
def test_connected_accounts_oauth_url_reads_create_connect_url(client):
|
|
1023
|
+
response = {"data": {"createConnectUrl": {"url": "https://oauth.example/x", "success": True}}}
|
|
1024
|
+
with requests_mock.Mocker() as m:
|
|
1025
|
+
m.post("https://listingsapi.com/api/v4/locations/oauth_connect_url", json=response)
|
|
1026
|
+
result = client.connected_accounts.oauth_url(
|
|
1027
|
+
16808, "google", "https://ok.example", "https://err.example"
|
|
1028
|
+
)
|
|
1029
|
+
assert result.url == "https://oauth.example/x"
|
|
1030
|
+
sent = m.last_request.json()["input"]
|
|
1031
|
+
assert sent["locationId"] == "TG9jYXRpb246MTY4MDg="
|
|
1032
|
+
assert sent["site"] == "GOOGLE"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|