Skip to content

Commit 98f71c7

Browse files
committed
fix: handle sandbox errors in examples and fix response None checks
- Fix to in customs client and SOAP transport (requests.Response.__bool__ is False for non-2xx status codes) - Extract API error messages from 400 response bodies in invoice client - Add _safe_int/_safe_float helpers for ListInvoices Georgian text fields - Add sandbox error handling to all examples (SOAP 500, auth, customs 404) - Use sandbox credentials in examples (satesto2/tbilisi) - Add create_declaration.py example
1 parent a2cb4e4 commit 98f71c7

12 files changed

Lines changed: 203 additions & 66 deletions

examples/basic_invoice.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
def main():
1717
with InvoiceClient() as client:
1818
# Step 1: Authenticate (handles both 1FA and 2FA)
19-
auth = client.authenticate('your_username', 'your_password')
19+
auth = client.authenticate('satesto2', '123456')
2020

2121
if auth.needs_pin:
2222
pin = input(f'Enter PIN sent to {auth.masked_mobile}: ')
@@ -25,14 +25,19 @@ def main():
2525
print(f'Authenticated (token: {auth.access_token[:20]}...)')
2626

2727
# Step 2: Create an invoice
28+
# Note: sandbox only allows test TINs 206322102 and 12345678910
2829
inv = Invoice(
29-
inv_category = InvoiceCategory.GOODS_SERVICE,
30-
inv_type = InvoiceType.WITH_TRANSPORT,
31-
operation_date = '10-04-2025 10:00:00',
32-
tin_seller = '206322102',
33-
tin_buyer = '12345678910',
30+
inv_category = InvoiceCategory.GOODS_SERVICE,
31+
inv_type = InvoiceType.WITH_TRANSPORT,
32+
operation_date = '10-02-2025 10:00:00',
33+
tin_seller = '12345678910',
34+
tin_buyer = '206322102',
3435
trans_start_address = 'თბილისი, რუსთაველის გამზ. 12',
3536
trans_end_address = 'რუსთავი, მშვიდობის ქ. 5',
37+
trans_type = 1,
38+
trans_car_no = 'AB-123-CD',
39+
trans_driver_tin = '206322102',
40+
trans_cost_payer = 2,
3641
)
3742

3843
# Step 3: Add goods (amount auto-calculated)

examples/basic_waybill.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
33
Demonstrates the full lifecycle of an electronic commodity waybill
44
using the RS.ge WayBill SOAP service.
5+
6+
Note: Some SOAP endpoints return 500 on the sandbox intermittently.
57
"""
68

79
from rsge import (
@@ -10,12 +12,17 @@
1012
WayBillClient,
1113
WayBillType,
1214
)
15+
from rsge.core.exceptions import RSGeAuthenticationError, RSGeConnectionError
1316

1417

1518
def main():
1619
with WayBillClient('tbilisi', '123456') as client:
17-
un_id, user_id = client.check_service_user()
18-
print(f'Authenticated — TIN: {un_id}, User ID: {user_id}')
20+
# check_service_user() may return false on sandbox test accounts
21+
try:
22+
un_id, user_id = client.check_service_user()
23+
print(f'Authenticated — TIN: {un_id}, User ID: {user_id}')
24+
except RSGeAuthenticationError:
25+
print('Note: check_service_user() not available for this sandbox account')
1926

2027
wb = client.create_waybill(
2128
waybill_type = WayBillType.TRANSPORTATION,
@@ -50,17 +57,20 @@ def main():
5057

5158
print(f'Goods: {len(wb.goods_list)} items, total: {wb.full_amount} GEL')
5259

53-
result = client.save_waybill(wb)
54-
if result.is_success:
55-
print(f'Saved — Waybill ID: {result.waybill_id}')
60+
try:
61+
result = client.save_waybill(wb)
62+
if result.is_success:
63+
print(f'Saved — Waybill ID: {result.waybill_id}')
5664

57-
wb_number = client.activate_waybill(result.waybill_id)
58-
print(f'Activated — Number: {wb_number}')
65+
wb_number = client.activate_waybill(result.waybill_id)
66+
print(f'Activated — Number: {wb_number}')
5967

60-
client.close_waybill(result.waybill_id)
61-
print('Closed — Delivery complete')
62-
else:
63-
print(f'Save failed: code {result.status}')
68+
client.close_waybill(result.waybill_id)
69+
print('Closed — Delivery complete')
70+
else:
71+
print(f'Save failed: code {result.status}')
72+
except (RSGeAuthenticationError, RSGeConnectionError) as exc:
73+
print(f'Note: Sandbox limitation: {exc}')
6474

6575

6676
if __name__ == '__main__':

examples/buyer_confirmation.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@
22
33
Demonstrates querying unconfirmed waybills as a buyer
44
and confirming or rejecting them.
5+
6+
Note: Some SOAP endpoints return 500 on the sandbox intermittently.
57
"""
68

79
from rsge import WayBillClient
10+
from rsge.core.exceptions import RSGeConnectionError
811

912

1013
def main():
1114
with WayBillClient('satesto2', '123456') as buyer:
12-
waybills = buyer.get_buyer_waybills_ex(
13-
statuses = ',2,',
14-
is_confirmed = 0,
15-
create_date_s = '2024-01-01',
16-
create_date_e = '2024-12-31',
17-
)
15+
try:
16+
waybills = buyer.get_buyer_waybills_ex(
17+
statuses = ',2,',
18+
is_confirmed = 0,
19+
create_date_s = '2024-01-01',
20+
create_date_e = '2024-12-31',
21+
)
22+
except RSGeConnectionError as exc:
23+
print(f'Note: SOAP service error (sandbox may be unstable): {exc}')
24+
return
1825

1926
print(f'Pending confirmation: {len(waybills)}')
2027

examples/create_declaration.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Monthly declaration for მცირე მეწარმე (small business, 1% tax).
2+
3+
Demonstrates listing invoices eligible for declaration binding,
4+
checking the sequence number, and creating a monthly declaration.
5+
6+
Note: create_decl() may fail on sandbox because test invoices
7+
don't meet the attachment conditions required for declarations.
8+
"""
9+
10+
from rsge import InvoiceClient, InvoiceListType
11+
from rsge.core.exceptions import RSGeAPIError
12+
13+
14+
def main():
15+
with InvoiceClient() as client:
16+
auth = client.authenticate('satesto2', '123456')
17+
18+
if auth.needs_pin:
19+
pin = input(f'Enter PIN sent to {auth.masked_mobile}: ')
20+
auth = client.authenticate_pin(auth.pin_token, pin)
21+
22+
print(f'Authenticated (token: {auth.access_token[:20]}...)')
23+
24+
year, month = 2025, 2
25+
26+
# Step 1: List seller invoices eligible for declaration
27+
invoices = client.list_invoices(TYPE=InvoiceListType.SELLER_DECL)
28+
29+
print(f'\nInvoices eligible for declaration: {len(invoices)}')
30+
for inv in invoices:
31+
print(f' #{inv.id} | {inv.inv_number} | {inv.amount_full} GEL')
32+
33+
if not invoices:
34+
print('No invoices to attach — nothing to declare')
35+
client.sign_out()
36+
return
37+
38+
# Step 2: Check sequence number
39+
seq = client.get_seq_num(year, month)
40+
print(f'\nSequence number ({year}/{month:02d}): {seq}')
41+
42+
# Step 3: Create monthly declaration with all eligible invoices
43+
invoice_ids = [inv.id for inv in invoices]
44+
try:
45+
client.create_decl(invoice_ids, year=year, month=month)
46+
print(f'Declaration created — {len(invoice_ids)} invoice(s) attached to {year}/{month:02d}')
47+
except RSGeAPIError as exc:
48+
print(f'Note: create_decl() not available on sandbox: {exc}')
49+
50+
client.sign_out()
51+
print('Signed out')
52+
53+
54+
if __name__ == '__main__':
55+
main()

examples/customs_declarations.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,22 @@
22
33
Demonstrates authenticating with the customs API,
44
fetching declarations by date range, and signing out.
5+
6+
Note: The customs API may not be available on the sandbox environment.
57
"""
68

79
from rsge import CustomsClient
10+
from rsge.core.exceptions import RSGeAPIError
811

912

1013
def main():
1114
with CustomsClient() as client:
12-
auth = client.authenticate('your_username', 'your_password')
15+
try:
16+
auth = client.authenticate('satesto2', '123456')
17+
except RSGeAPIError as exc:
18+
print(f'Note: Customs API auth not available on sandbox: {exc}')
19+
return
20+
1321
print(f'Authenticated (token: {auth.access_token[:20]}...)')
1422

1523
declarations = client.get_declarations(

examples/invoice_management.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
def main():
1111
with InvoiceClient() as client:
12-
client.authenticate('your_username', 'your_password')
12+
client.authenticate('satesto2', '123456')
1313

1414
# List seller's own documents
1515
seller_docs = client.list_invoices(

examples/query_waybills.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,38 @@
22
33
Demonstrates listing waybills with filters and retrieving
44
full waybill details by ID.
5+
6+
Note: Some SOAP endpoints return 500 on the sandbox intermittently.
57
"""
68

79
from rsge import WayBillClient, WayBillType
10+
from rsge.core.exceptions import RSGeConnectionError
811

912

1013
def main():
1114
with WayBillClient('tbilisi', '123456') as client:
12-
waybills = client.get_waybills(
13-
statuses = ',1,',
14-
create_date_s = '2024-01-01',
15-
create_date_e = '2024-01-31',
16-
)
15+
try:
16+
waybills = client.get_waybills(
17+
statuses = ',1,',
18+
create_date_s = '2024-01-01',
19+
create_date_e = '2024-01-31',
20+
)
21+
except RSGeConnectionError as exc:
22+
print(f'Note: SOAP service error (sandbox may be unstable): {exc}')
23+
return
1724

1825
print(f'Seller waybills: {len(waybills)}')
1926
for wb in waybills[:5]:
2027
print(f' #{wb.id} | {wb.waybill_number} | {wb.buyer_name} | {wb.full_amount} GEL')
2128

22-
buyer_wbs = client.get_buyer_waybills(
23-
create_date_s = '2024-01-01',
24-
create_date_e = '2024-01-31',
25-
)
29+
try:
30+
buyer_wbs = client.get_buyer_waybills(
31+
create_date_s = '2024-01-01',
32+
create_date_e = '2024-01-31',
33+
)
34+
except RSGeConnectionError as exc:
35+
print(f'Note: SOAP service error (sandbox may be unstable): {exc}')
36+
return
2637

2738
print(f'Buyer waybills: {len(buyer_wbs)}')
2839

examples/templates.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
33
Demonstrates creating a waybill template, listing templates,
44
loading a template, and creating a waybill from it.
5+
6+
Note: Some SOAP endpoints return 500 on the sandbox intermittently.
57
"""
68

79
from rsge import WayBillClient, WayBillType
10+
from rsge.core.exceptions import RSGeConnectionError
811

912

1013
def main():
@@ -25,18 +28,21 @@ def main():
2528
bar_code = 'STD01',
2629
)
2730

28-
client.save_waybill_template('ბათუმის მარშრუტი', wb)
29-
print('Template saved')
30-
31-
templates = client.get_waybill_templates()
32-
for t in templates:
33-
print(f" Template #{t['id']}: {t['name']}")
34-
35-
if templates:
36-
loaded = client.get_waybill_template(templates[0]['id'])
37-
loaded.buyer_name = 'შპს ახალი მყიდველი'
38-
result = client.save_waybill(loaded)
39-
print(f'Waybill from template: {result.waybill_id}')
31+
try:
32+
client.save_waybill_template('ბათუმის მარშრუტი', wb)
33+
print('Template saved')
34+
35+
templates = client.get_waybill_templates()
36+
for t in templates:
37+
print(f" Template #{t['id']}: {t['name']}")
38+
39+
if templates:
40+
loaded = client.get_waybill_template(templates[0]['id'])
41+
loaded.buyer_name = 'შპს ახალი მყიდველი'
42+
result = client.save_waybill(loaded)
43+
print(f'Waybill from template: {result.waybill_id}')
44+
except RSGeConnectionError as exc:
45+
print(f'Note: SOAP service error (sandbox may be unstable): {exc}')
4046

4147

4248
if __name__ == '__main__':

rsge/core/transport.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,9 @@ def call(
8989
f'Request to RS.ge timed out after {self._timeout}s'
9090
) from exc
9191
except requests.exceptions.HTTPError as exc:
92+
status_code = exc.response.status_code if exc.response is not None else 0
9293
raise RSGeConnectionError(
93-
f'HTTP error from RS.ge: {exc.response.status_code}'
94+
f'HTTP error from RS.ge: {status_code}'
9495
) from exc
9596

9697
return self._parse_response(response.text, method)

rsge/customs/client.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ def get_declarations(
181181
raise RSGeConnectionError(f'Connection failed: {exc}') from exc
182182

183183
except requests.exceptions.HTTPError as exc:
184-
raise RSGeAPIError(f'HTTP error: {exc.response.status_code}') from exc
184+
status_code = exc.response.status_code if exc.response is not None else 0
185+
raise RSGeAPIError(f'HTTP error: {status_code}') from exc
185186

186187
data = response.json()
187188
status = data.get('STATUS', {})
@@ -220,7 +221,7 @@ def _post(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
220221
raise RSGeConnectionError(f'Connection failed: {exc}') from exc
221222

222223
except requests.exceptions.HTTPError as exc:
223-
status_code = exc.response.status_code if exc.response else 0
224+
status_code = exc.response.status_code if exc.response is not None else 0
224225
raise RSGeAPIError(f'HTTP error: {status_code}') from exc
225226

226227
result: dict[str, Any] = response.json()

0 commit comments

Comments
 (0)