spyreapi 0.0.1__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.
spyre/Exceptions.py ADDED
@@ -0,0 +1,45 @@
1
+
2
+ """
3
+ >Cannot Convert Invoice to Quote
4
+ >Inactive Item
5
+ >invalide Syntax for date time
6
+ >A database error has occurred:\n\nA value passed to the database was too long for the column - Country Name
7
+ >Order Number Exists
8
+ >Invalid Customer
9
+ >Missing Parent/Child errror -> id of addresses
10
+
11
+ Contacts 3
12
+ Request Failure
13
+ Not Found
14
+ invalid Field for filter
15
+
16
+
17
+ {'status_code': 422, 'url': 'https://red-wave-8362.spirelan.com:10880/api/v2/companies/intertest/sales/orders/31308/invoice',
18
+ 'content': {'type': 'error', 'message': 'Cannot post a deleted order', 'traceback': '', 'error_type': 'BusinessViolationError'}}
19
+ """
20
+
21
+ class CreateRequestError(Exception):
22
+ """
23
+ Exception raised when a create (POST) request to the API fails.
24
+ """
25
+
26
+ def __init__(self, endpoint: str, status_code: int, error_message: str = "", response_body: dict = None):
27
+ self.endpoint = endpoint
28
+ self.status_code = status_code
29
+ self.error_message = error_message or "Unknown error occurred"
30
+ self.response_body = response_body or {}
31
+
32
+ super().__init__(self.__str__())
33
+
34
+ def __str__(self):
35
+ return (
36
+ f"Create request to '{self.endpoint}' failed with status {self.status_code}: {self.error_message}"
37
+ )
38
+
39
+ def to_dict(self):
40
+ return {
41
+ "endpoint": self.endpoint,
42
+ "status_code": self.status_code,
43
+ "error_message": self.error_message,
44
+ "response_body": self.response_body,
45
+ }
File without changes
@@ -0,0 +1,40 @@
1
+ from typing import List, Optional, Dict, Union, Any
2
+ from pydantic import BaseModel, model_validator
3
+ from Models.shared_models import *
4
+
5
+ class Customer(BaseModel):
6
+ id: Optional[int] = None
7
+ code: Optional[str] = None
8
+ customerNo: Optional[str] = None
9
+ name: Optional[str] = None
10
+ foregroundColor: Optional[int] = None
11
+ backgroundColor: Optional[int] = None
12
+ hold: Optional[bool] = None
13
+ status: Optional[str] = None
14
+ reference: Optional[str] = None
15
+ address: Optional[Address] = None
16
+ shippingAddresses: Optional[List[Address]] = None
17
+ paymentTerms: Optional[Dict[str, Any]] = None
18
+ applyFinanceCharges: Optional[bool] = None
19
+ statementType: Optional[str] = None
20
+ creditType: Optional[int] = None
21
+ creditLimit: Optional[str] = None
22
+ creditBalance: Optional[str] = None
23
+ creditApprovedBy: Optional[str] = None
24
+ creditApprovedDate: Optional[str] = None
25
+ currency: Optional[str] = None
26
+ userDef1: Optional[str] = None
27
+ userDef2: Optional[str] = None
28
+ discount: Optional[str] = None
29
+ receivableAccount: Optional[str] = None
30
+ defaultShipTo: Optional[str] = None
31
+ specialCode: Optional[str] = None
32
+ upload: Optional[bool] = None
33
+ lastModified: Optional[str] = None
34
+ paymentProviderId: Optional[int] = None
35
+ udf: Optional[Dict[str, Any]] = None
36
+ createdBy: Optional[str] = None
37
+ modifiedBy: Optional[str] = None
38
+ created: Optional[str] = None
39
+ modified: Optional[str] = None
40
+ links: Optional[Dict[str, str]] = None
@@ -0,0 +1,160 @@
1
+ from typing import List, Dict, Optional, Union
2
+ from pydantic import BaseModel
3
+
4
+
5
+
6
+ class UPC(BaseModel):
7
+ id: Optional[int] = None
8
+ whse: Optional[str] = None
9
+ partNo: Optional[str] = None
10
+ inventory: Optional[Dict] = None
11
+ uomCode: Optional[str] = None
12
+ upc: Optional[str] = None
13
+ created: Optional[str] = None
14
+ createdBy: Optional[str] = None
15
+ modified: Optional[str] = None
16
+ modifiedBy: Optional[str] = None
17
+ links: Optional[Dict[str, str]] = None
18
+
19
+ class Vendor(BaseModel):
20
+ id: Optional[int] = None
21
+ vendorNo: Optional[str] = None
22
+ name: Optional[str] = None
23
+
24
+
25
+ class UnitOfMeasure(BaseModel):
26
+ id: Optional[int] = None
27
+ code: Optional[str] = None
28
+ description: Optional[str] = None
29
+ location: Optional[str] = None
30
+ weight: Optional[str] = None
31
+ buyUOM: Optional[bool] = None
32
+ sellUOM: Optional[bool] = None
33
+ allowFractionalQty: Optional[bool] = None
34
+ quantityFactor: Optional[str] = None
35
+ directFactor: Optional[bool] = None
36
+
37
+
38
+ class Pricing(BaseModel):
39
+ id: Optional[int] = None
40
+ sellPrices: Optional[List[str]] = None
41
+
42
+
43
+ class ItemUDF(BaseModel):
44
+ desc: Optional[str] = None
45
+ brand: Optional[str] = None
46
+ model: Optional[str] = None
47
+ title: Optional[str] = None
48
+ online: Optional[bool] = None
49
+ pk_qty: Optional[str] = None
50
+ volume: Optional[str] = None
51
+ presale: Optional[bool] = None
52
+ sds_url: Optional[str] = None
53
+ tds_url: Optional[str] = None
54
+ features: Optional[str] = None
55
+ includes: Optional[str] = None
56
+ keywords: Optional[str] = None
57
+ op1_name: Optional[str] = None
58
+ op2_name: Optional[str] = None
59
+ op3_name: Optional[str] = None
60
+ op4_name: Optional[str] = None
61
+ asm_depth: Optional[str] = None
62
+ asm_width: Optional[str] = None
63
+ category1: Optional[str] = None
64
+ category2: Optional[str] = None
65
+ category3: Optional[str] = None
66
+ category4: Optional[str] = None
67
+ op1_value: Optional[str] = None
68
+ op2_value: Optional[str] = None
69
+ op3_value: Optional[str] = None
70
+ op4_value: Optional[str] = None
71
+ pkg_depth: Optional[str] = None
72
+ pkg_width: Optional[str] = None
73
+ asm_height: Optional[str] = None
74
+ asm_length: Optional[str] = None
75
+ asm_weight: Optional[str] = None
76
+ html_specs: Optional[str] = None
77
+ image_urls: Optional[str] = None
78
+ is_variant: Optional[bool] = None
79
+ pkg_height: Optional[str] = None
80
+ pkg_length: Optional[str] = None
81
+ pkg_weight: Optional[str] = None
82
+ put_online: Optional[bool] = None
83
+ upc_needed: Optional[bool] = None
84
+ video_urls: Optional[str] = None
85
+ hdr_part_no: Optional[str] = None
86
+ applications: Optional[str] = None
87
+ sell_through: Optional[bool] = None
88
+ unique_title: Optional[str] = None
89
+ alternate_res: Optional[str] = None
90
+ asm_weight_op: Optional[str] = None
91
+ html_includes: Optional[str] = None
92
+ more_info_url: Optional[str] = None
93
+ specsheet_url: Optional[str] = None
94
+ html_desc_feat: Optional[str] = None
95
+ specifications: Optional[str] = None
96
+ html_dimensions: Optional[str] = None
97
+ special_delivery: Optional[bool] = None
98
+ alternate_res_src: Optional[str] = None
99
+ html_applications: Optional[str] = None
100
+
101
+
102
+ class InventoryItem(BaseModel):
103
+ id: Optional[int] = None
104
+ whse: Optional[str] = None
105
+ partNo: Optional[str] = None
106
+ description: Optional[str] = None
107
+ type: Optional[str] = None
108
+ status: Optional[int] = None
109
+ lotNumbered: Optional[bool] = None
110
+ serialized: Optional[bool] = None
111
+ availableQty: Optional[str] = None
112
+ onHandQty: Optional[str] = None
113
+ committedQty: Optional[str] = None
114
+ backorderQty: Optional[str] = None
115
+ onPurchaseQty: Optional[str] = None
116
+ foregroundColor: Optional[int] = None
117
+ backgroundColor: Optional[int] = None
118
+ primaryVendor: Optional[Vendor] = None
119
+ currentPONo: Optional[str] = None
120
+ poDueDate: Optional[str] = None
121
+ reorderPoint: Optional[str] = None
122
+ minimumBuyQty: Optional[str] = None
123
+ currentCost: Optional[str] = None
124
+ averageCost: Optional[str] = None
125
+ standardCost: Optional[str] = None
126
+ unitOfMeasures: Optional[Dict[str, UnitOfMeasure]] = None
127
+ buyMeasureCode: Optional[str] = None
128
+ stockMeasureCode: Optional[str] = None
129
+ sellMeasureCode: Optional[str] = None
130
+ alternatePartNo: Optional[str] = None
131
+ productCode: Optional[str] = None
132
+ groupNo: Optional[str] = None
133
+ salesDept: Optional[str] = None
134
+ userDef1: Optional[str] = None
135
+ userDef2: Optional[str] = None
136
+ discountable: Optional[bool] = None
137
+ weight: Optional[str] = None
138
+ packSize: Optional[str] = None
139
+ allowBackorders: Optional[bool] = None
140
+ allowReturns: Optional[bool] = None
141
+ dutyPct: Optional[str] = None
142
+ freightPct: Optional[str] = None
143
+ manufactureCountry: Optional[str] = None
144
+ harmonizedCode: Optional[str] = None
145
+ extendedDescription: Optional[str] = None
146
+ pricing: Optional[Dict[str, Pricing]] = None
147
+ salesTaxFlags: Optional[Dict[str, Union[str, int, float, bool]]] = None
148
+ images: Optional[List[str]] = None
149
+ defaultExpiryDate: Optional[str] = None
150
+ lotConsumeType: Optional[str] = None
151
+ upload: Optional[bool] = None
152
+ showOptions: Optional[bool] = None
153
+ lastModified: Optional[str] = None
154
+ levy: Optional[str] = None
155
+ udf: Optional[ItemUDF] = None
156
+ createdBy: Optional[str] = None
157
+ modifiedBy: Optional[str] = None
158
+ created: Optional[str] = None
159
+ modified: Optional[str] = None
160
+ links: Optional[Dict[str, Union[str, int, float, bool]]] = None
@@ -0,0 +1,170 @@
1
+ from typing import List, Optional, Dict, Union
2
+ from pydantic import BaseModel, model_validator
3
+ from Models.shared_models import *
4
+ from Models.customers_models import Customer
5
+
6
+ class Inventory(BaseModel):
7
+ id: Optional[int] = None
8
+ whse: Optional[str] = None
9
+ partNo: Optional[str] = None
10
+ description: Optional[str] = None
11
+
12
+ class SalesOrderItem(BaseModel):
13
+ id: Optional[int] = None
14
+ orderNo: Optional[str] = None
15
+ sequence: Optional[int] = None
16
+ parentSequence: Optional[int] = None
17
+ inventory: Optional[Inventory] = None
18
+ serials: Optional[str] = None
19
+ whse: Optional[str] = None
20
+ partNo: Optional[str] = None
21
+ description: Optional[str] = None
22
+ comment: Optional[str] = None
23
+ orderQty: Optional[str] = None
24
+ committedQty: Optional[str] = None
25
+ backorderQty: Optional[str] = None
26
+ sellMeasure: Optional[str] = None
27
+ retailPrice: Optional[str] = None
28
+ unitPrice: Optional[str] = None
29
+ userPrice: Optional[bool] = None
30
+ discountable: Optional[bool] = None
31
+ discountPct: Optional[str] = None
32
+ discountAmt: Optional[str] = None
33
+ currentCost: Optional[str] = None
34
+ averageCost: Optional[str] = None
35
+ standardCost: Optional[str] = None
36
+ taxFlags: Optional[List[bool]] = None
37
+ vendor: Optional[str] = None
38
+ inventoryAccountNo: Optional[str] = None
39
+ revenueAccountNo: Optional[str] = None
40
+ costOfGoodsAccountNo: Optional[str] = None
41
+ levyCode: Optional[str] = None
42
+ referenceNo: Optional[str] = None
43
+ requiredDate: Optional[str] = None
44
+ extendedPriceOrdered: Optional[str] = None
45
+ extendedPriceCommitted: Optional[str] = None
46
+ kit: Optional[bool] = None
47
+ suppress: Optional[bool] = None
48
+ udf: Optional[dict] = None
49
+
50
+
51
+ class SalesOrder(BaseModel):
52
+ id: Optional[int] = None
53
+ orderNo: Optional[str] = None
54
+ division: Optional[str] = None
55
+ location: Optional[str] = None
56
+ profitCenter: Optional[str] = None
57
+ invoiceNo: Optional[str] = None
58
+ customer: Optional[Customer] = None
59
+ creditApprovedAmount: Optional[str] = None
60
+ creditApprovedDate: Optional[str] = None
61
+ creditApprovedUser: Optional[str] = None
62
+ currency: Optional[Currency] = None
63
+ status: Optional[str] = None
64
+ type: Optional[str] = None
65
+ hold: Optional[bool] = None
66
+ orderDate: Optional[str] = None
67
+ invoiceDate: Optional[str] = None
68
+ requiredDate: Optional[str] = None
69
+ quoteExpires: Optional[str] = None
70
+ recurrenceRule: Optional[str] = None
71
+ address: Optional[Address] = None
72
+ shippingAddress: Optional[Address] = None
73
+ contact: Optional[Contact] = None
74
+ customerPO: Optional[str] = None
75
+ batchNo: Optional[str] = None
76
+ fob: Optional[str] = None
77
+ incoterms: Optional[str] = None
78
+ incotermsPlace: Optional[str] = None
79
+ referenceNo: Optional[str] = None
80
+ shippingCarrier: Optional[str] = None
81
+ shipDate: Optional[str] = None
82
+ trackingNo: Optional[str] = None
83
+ termsCode: Optional[str] = None
84
+ termsText: Optional[str] = None
85
+ freight: Optional[str] = None
86
+ taxes: Optional[List[Tax]] = None
87
+ subtotal: Optional[str] = None
88
+ subtotalOrdered: Optional[str] = None
89
+ discount: Optional[str] = None
90
+ totalDiscount: Optional[str] = None
91
+ total: Optional[str] = None
92
+ totalOrdered: Optional[str] = None
93
+ totalCostCurrent: Optional[str] = None
94
+ totalCostAverage: Optional[str] = None
95
+ grossProfit: Optional[str] = None
96
+ items: Optional[List[SalesOrderItem]] = None
97
+ payments: Optional[List[dict]] = None
98
+ udf: Optional[dict] = None
99
+ createdBy: Optional[str] = None
100
+ modifiedBy: Optional[str] = None
101
+ created: Optional[str] = None
102
+ modified: Optional[str] = None
103
+ deletedBy: Optional[str] = None
104
+ deleted: Optional[str] = None
105
+ links: Optional[Dict[str, str]] = None
106
+
107
+ @model_validator(mode="before")
108
+ @classmethod
109
+ def clean_problematic_fields(cls, data: dict) -> dict:
110
+ if data.get("currency") == "":
111
+ data["currency"] = None
112
+
113
+ contact = data.get("contact")
114
+ if contact:
115
+ for field in ("phone", "fax"):
116
+ phone_data = contact.get(field)
117
+ if isinstance(phone_data, dict) and phone_data.get("number") is None:
118
+ phone_data["number"] = ""
119
+ return data
120
+
121
+ class Invoice(BaseModel):
122
+ id: Optional[int] = None
123
+ invoiceNo: Optional[str] = None
124
+ orderNo: Optional[str] = None
125
+ division: Optional[str] = None
126
+ location: Optional[str] = None
127
+ profitCenter: Optional[str] = None
128
+ customer: Optional['Customer'] = None
129
+ currency: Optional['Currency'] = None
130
+ orderDate: Optional[str] = None
131
+ invoiceDate: Optional[str] = None
132
+ requiredDate: Optional[str] = None
133
+ address: Optional['Address'] = None
134
+ shippingAddress: Optional['Address'] = None
135
+ customerPO: Optional[str] = None
136
+ fob: Optional[str] = None
137
+ incoterms: Optional[str] = None
138
+ incotermsPlace: Optional[str] = None
139
+ referenceNo: Optional[str] = None
140
+ shippingCarrier: Optional[str] = None
141
+ shipDate: Optional[str] = None
142
+ trackingNo: Optional[str] = None
143
+ termsCode: Optional[str] = None
144
+ termsText: Optional[str] = None
145
+ freight: Optional[str] = None
146
+ taxes: Optional[List['Tax']] = None
147
+ subtotal: Optional[str] = None
148
+ total: Optional[str] = None
149
+ items: Optional[List['SalesOrderItem']] = None
150
+ payments: Optional[List[dict]] = None
151
+ udf: Optional[dict] = None
152
+ createdBy: Optional[str] = None
153
+ modifiedBy: Optional[str] = None
154
+ created: Optional[str] = None
155
+ modified: Optional[str] = None
156
+ links: Optional[Dict[str, str]] = None
157
+
158
+ @model_validator(mode="before")
159
+ @classmethod
160
+ def clean_problematic_fields(cls, data: dict) -> dict:
161
+ if data.get("currency") == "":
162
+ data["currency"] = None
163
+
164
+ contact = data.get("contact")
165
+ if contact:
166
+ for field in ("phone", "fax"):
167
+ phone_data = contact.get(field)
168
+ if isinstance(phone_data, dict) and phone_data.get("number") is None:
169
+ phone_data["number"] = ""
170
+ return data
@@ -0,0 +1,96 @@
1
+ from typing import List, Optional, Dict, Union
2
+ from pydantic import BaseModel, model_validator
3
+
4
+ class PhoneFax(BaseModel):
5
+ number: str
6
+ format: Optional[int] = 1
7
+
8
+ class Contact(BaseModel):
9
+ id: Optional[int] = None
10
+ contact_type: Optional[dict] = None
11
+ name: Optional[str] = None
12
+ email: Optional[str] = None
13
+ phone: Optional[PhoneFax] = None
14
+ fax: Optional[PhoneFax] = None
15
+
16
+
17
+ class Salesperson(BaseModel):
18
+ code: Optional[str] = None
19
+ name: Optional[str] = None
20
+
21
+ class Territory(BaseModel):
22
+ code: Optional[str] = None
23
+ description: Optional[str] = None
24
+
25
+
26
+ class Currency(BaseModel):
27
+ id: Optional[int] = None
28
+ code: Optional[str] = None
29
+ description: Optional[str] = None
30
+ country: Optional[str] = None
31
+ units: Optional[str] = None
32
+ fraction: Optional[str] = None
33
+ symbol: Optional[str] = None
34
+ decimalPlaces: Optional[int] = None
35
+ symbolPosition: Optional[str] = None
36
+ rate: Optional[str] = None
37
+ rateMethod: Optional[str] = None
38
+ glAccountNo: Optional[str] = None
39
+ thousandsSeparator: Optional[str] = None
40
+ lastYearRate: Optional[List[str]] = None
41
+ thisYearRate: Optional[List[str]] = None
42
+ nextYearRate: Optional[List[str]] = None
43
+
44
+
45
+ class Tax(BaseModel):
46
+ code: Optional[int] = None
47
+ name: Optional[str] = None
48
+ shortName: Optional[str] = None
49
+ rate: Optional[Union[str, int]] = None
50
+ exemptNo: Optional[str] = None
51
+ total: Optional[Union[str, int]] = None
52
+
53
+
54
+ class Address(BaseModel):
55
+ id: Optional[int] = None
56
+ type: Optional[str] = None
57
+ linkTable: Optional[str] = None
58
+ linkType: Optional[str] = None
59
+ linkNo: Optional[str] = None
60
+ shipId: Optional[str] = None
61
+ name: Optional[str] = None
62
+ line1: Optional[str] = None
63
+ line2: Optional[str] = None
64
+ line3: Optional[str] = None
65
+ line4: Optional[str] = None
66
+ city: Optional[str] = None
67
+ postalCode: Optional[str] = None
68
+ provState: Optional[str] = None
69
+ country: Optional[str] = None
70
+ phone: Optional[PhoneFax] = None
71
+ fax: Optional[PhoneFax] = None
72
+ email: Optional[str] = None
73
+ website: Optional[str] = None
74
+ shipCode: Optional[str] = None
75
+ shipDescription: Optional[str] = None
76
+ salesperson: Optional[Salesperson] = None
77
+ territory: Optional[Territory] = None
78
+ sellLevel: Optional[int] = None
79
+ glAccount: Optional[str] = None
80
+ defaultWarehouse: Optional[str] = None
81
+ udf: Optional[dict] = None
82
+ created: Optional[str] = None
83
+ modified: Optional[str] = None
84
+ contacts: Optional[List[Contact]] = None
85
+ salesTaxes: Optional[List[Dict[str, Union[int, str]]]] = None
86
+
87
+ @model_validator(mode="before")
88
+ @classmethod
89
+ def limit_contacts_to_three(cls, data):
90
+ """Limits contacts to 3 per address. The Spire API only allows Creatig/Updating Addresses with contacts."""
91
+
92
+ if isinstance(data, dict):
93
+ contacts = data.get('contacts')
94
+ if isinstance(contacts, list) and len(contacts) > 3:
95
+ data['contacts'] = contacts[:3]
96
+ return data
spyre/__init__.py ADDED
@@ -0,0 +1,22 @@
1
+ from .client import SpireClient
2
+ from .inventory import InventoryClient
3
+ from .sales import OrdersClient, InvoiceClient, salesOrder, invoice
4
+ from .Models.sales_models import SalesOrder, SalesOrderItem
5
+ from .Models.inventory_models import InventoryItem, Vendor, UnitOfMeasure, Pricing, UPC
6
+
7
+ __all__ = [
8
+ "SpireClient",
9
+ "InventoryClient",
10
+ "SalesOrderClient",
11
+ "SalesOrder",
12
+ "SalesOrderItem",
13
+ "InventoryItem",
14
+ "Vendor",
15
+ "UnitOfMeasure",
16
+ "Pricing",
17
+ "UPC",
18
+ "OrdersClient",
19
+ "InvoiceClient",
20
+ "salesOrder",
21
+ "invoice"
22
+ ]