spyreapi 0.0.3__tar.gz → 0.0.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.
Files changed (28) hide show
  1. {spyreapi-0.0.3/src/spyreapi.egg-info → spyreapi-0.0.4}/PKG-INFO +26 -8
  2. spyreapi-0.0.4/README.md +57 -0
  3. {spyreapi-0.0.3 → spyreapi-0.0.4}/pyproject.toml +1 -1
  4. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/Models/inventory_models.py +7 -3
  5. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/client.py +77 -12
  6. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/inventory.py +1 -1
  7. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/sales.py +3 -6
  8. spyreapi-0.0.4/src/spyre/spire.py +23 -0
  9. {spyreapi-0.0.3 → spyreapi-0.0.4/src/spyreapi.egg-info}/PKG-INFO +26 -8
  10. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyreapi.egg-info/SOURCES.txt +0 -1
  11. spyreapi-0.0.3/README.md +0 -39
  12. spyreapi-0.0.3/src/spyre/config.py +0 -8
  13. spyreapi-0.0.3/src/spyre/spire.py +0 -14
  14. {spyreapi-0.0.3 → spyreapi-0.0.4}/LICENSE +0 -0
  15. {spyreapi-0.0.3 → spyreapi-0.0.4}/MANIFEST.in +0 -0
  16. {spyreapi-0.0.3 → spyreapi-0.0.4}/setup.cfg +0 -0
  17. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/Exceptions.py +0 -0
  18. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/Models/__init__.py +0 -0
  19. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/Models/customers_models.py +0 -0
  20. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/Models/sales_models.py +0 -0
  21. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/Models/shared_models.py +0 -0
  22. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/__init__.py +0 -0
  23. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/crm.py +0 -0
  24. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/customers.py +0 -0
  25. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyre/utils.py +0 -0
  26. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyreapi.egg-info/dependency_links.txt +0 -0
  27. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyreapi.egg-info/requires.txt +0 -0
  28. {spyreapi-0.0.3 → spyreapi-0.0.4}/src/spyreapi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spyreapi
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: A robust and extensible Python client for interacting with the [Spire Business Software API](https://developer.spiresystems.com/reference). This client provides an object-oriented interface to get, create, update, delete, query, filter, sort, and manage various Spire modules such as Sales Orders, Invoices, Inventory Items, and more.
5
5
  Author-email: Sanjid Sharaf <sanjidsharaf1@gmail.com>
6
6
  License-Expression: MIT
@@ -48,14 +48,32 @@ pip install -r requirements.txt
48
48
 
49
49
  ## ⚙️ Configuration
50
50
 
51
- - Before using the client, set up your environment configuration:
51
+ - How to set up your spire client
52
52
 
53
- ### Add your Base URL to your spire server to a `.env` file
53
+ ### Find your Spire URL
54
54
 
55
- - In your project root, create a `.env` file to securely store your Spire configuration.
56
- - Add the following variable:
55
+ The base URL for the Spire API is the same url provided by Spire that you use to access Spire server and uses port 10880 as default:
56
+ Replace {spire-url} with the url provided by Spire.
57
57
 
58
- ```env
59
- BASE_URL = https://{your-spire-domain}/api/v2/companies/
58
+ - https://{spire-url}:10880/api/v2/
59
+
60
+ Spire Cloud
61
+ If you are using Spire cloud you do not need to specify a port. The base URL for API for Spire Cloud customers would be:
62
+
63
+ - https://{spire-cloud-url}/api/v2/
64
+
65
+ ### Set up your client with your credentials
66
+
67
+ ```python
68
+ from spyre import Spire
69
+ # host is your spire url and the port if applicable
70
+ client = Spire(host = 'your-spire-host', company = 'comapany-name' , username = 'username' , password = 'password' )
71
+
72
+ ```
73
+
74
+ ## Example : Updating the status of an inventory item
75
+ ```python
76
+ item = client.inventory.items.get_item(1101) # Gets item with id 1101
77
+ item.status = 1 # Use either item. or item.model. . item.model. will bring up all attributes
78
+ item.update()
60
79
  ```
61
- - Replace {your-spire-domain} with your actual Spire server's hostname or IP address.
@@ -0,0 +1,57 @@
1
+ # Spire API Python Client
2
+
3
+ A robust and extensible Python client for interacting with the [Spire Business Software API](https://developer.spiresystems.com/reference). This client provides an object-oriented interface to get, create, update, delete, query, filter, sort, and manage various Spire modules such as Sales Orders, Invoices, Inventory Items, and more.
4
+
5
+ ---
6
+
7
+ ## ✨ Features
8
+
9
+ - ✅ Object-oriented resource wrappers for each module (e.g., `salesOrder`, `invoice`, `item`)
10
+ - 🔍 Full-text search via `q` parameter
11
+ - 🔁 Pagination with `start` and `limit` support
12
+ - 🧾 JSON-based advanced filtering (supports `$gt`, `$lt`, `$in`, `$or`, etc.)
13
+ - ↕️ Multi-field sorting with ascending/descending control
14
+ - 🔧 Clean abstraction layer for API endpoints
15
+ - 📦 Powered by `pydantic` models for validation
16
+
17
+ ---
18
+
19
+ ## 📦 Installation
20
+
21
+ ```bash
22
+ pip install -r requirements.txt
23
+ ```
24
+
25
+ ---
26
+
27
+ ## ⚙️ Configuration
28
+
29
+ - How to set up your spire client
30
+
31
+ ### Find your Spire URL
32
+
33
+ The base URL for the Spire API is the same url provided by Spire that you use to access Spire server and uses port 10880 as default:
34
+ Replace {spire-url} with the url provided by Spire.
35
+
36
+ - https://{spire-url}:10880/api/v2/
37
+
38
+ Spire Cloud
39
+ If you are using Spire cloud you do not need to specify a port. The base URL for API for Spire Cloud customers would be:
40
+
41
+ - https://{spire-cloud-url}/api/v2/
42
+
43
+ ### Set up your client with your credentials
44
+
45
+ ```python
46
+ from spyre import Spire
47
+ # host is your spire url and the port if applicable
48
+ client = Spire(host = 'your-spire-host', company = 'comapany-name' , username = 'username' , password = 'password' )
49
+
50
+ ```
51
+
52
+ ## Example : Updating the status of an inventory item
53
+ ```python
54
+ item = client.inventory.items.get_item(1101) # Gets item with id 1101
55
+ item.status = 1 # Use either item. or item.model. . item.model. will bring up all attributes
56
+ item.update()
57
+ ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spyreapi"
7
- version = "0.0.3"
7
+ version = "0.0.4"
8
8
  authors = [
9
9
  { name="Sanjid Sharaf", email="sanjidsharaf1@gmail.com" },
10
10
  ]
@@ -37,7 +37,11 @@ class UnitOfMeasure(BaseModel):
37
37
 
38
38
  class Pricing(BaseModel):
39
39
  id: Optional[int] = None
40
- sellPrices: Optional[List[str]] = None
40
+ sellPrice: Optional[List[str]] = None
41
+ currMargin: Optional[str] = None
42
+ currMarginPct: Optional[str] = None
43
+ avgMargin: Optional[str] = None
44
+ avgMarginPct: Optional[str] = None
41
45
 
42
46
 
43
47
  class ItemUDF(BaseModel):
@@ -143,7 +147,7 @@ class InventoryItem(BaseModel):
143
147
  manufactureCountry: Optional[str] = None
144
148
  harmonizedCode: Optional[str] = None
145
149
  extendedDescription: Optional[str] = None
146
- pricing: Optional[Dict[str, Pricing]] = None
150
+ pricing: Optional[Union[ Pricing | Dict[str, Pricing]]] = None
147
151
  salesTaxFlags: Optional[Dict[str, Union[str, int, float, bool]]] = None
148
152
  images: Optional[List[str]] = None
149
153
  defaultExpiryDate: Optional[str] = None
@@ -151,7 +155,7 @@ class InventoryItem(BaseModel):
151
155
  upload: Optional[bool] = None
152
156
  showOptions: Optional[bool] = None
153
157
  lastModified: Optional[str] = None
154
- levy: Optional[str] = None
158
+ levy: Optional[Union[str | dict]] = None
155
159
  udf: Optional[ItemUDF] = None
156
160
  createdBy: Optional[str] = None
157
161
  modifiedBy: Optional[str] = None
@@ -1,22 +1,46 @@
1
1
  import requests
2
- from .config import BASE_URL
3
2
  from typing import TypeVar, Optional, Type, Generic, List, Union, Tuple, Dict, Any
4
3
  from pydantic import BaseModel
5
4
  import json
6
5
  import urllib.parse
6
+ from requests.exceptions import HTTPError, ConnectionError, Timeout, RequestException
7
7
 
8
8
  T = TypeVar('T', bound=BaseModel)
9
9
 
10
- class SpireClient():
11
-
12
- def __init__(self, company, username, password,):
10
+ class SpireClient():
11
+ """A lightweight client to interact with the Spire API using requests sessions for connection reuse and authenticated calls."""
12
+ def __init__(self, host, company, username, password,):
13
+ """
14
+ :param host (str): Spire Server host
15
+ :param company (str): Spire company
16
+ :param username (str): Spire user username.
17
+ :param password (str): Spire user password.
18
+
19
+ """
13
20
  self.session = requests.Session()
14
21
  self.session.auth = (username, password)
15
22
  self.session.headers.update({
16
23
  "accept": "application/json",
17
24
  "content-type": "application/json"
18
25
  })
19
- self.base_url = f"{BASE_URL}/{company}"
26
+ self.base_url = f"https://{host}/api/v2/companies/{company}"
27
+
28
+ try:
29
+ response = self.session.get(self.base_url)
30
+ if response.text == 'No such company intertes':
31
+ raise ValueError(f"No company entries for {company}")
32
+ if response.text == 'Unauthorized':
33
+ raise ValueError(f"Invalid Authorization")
34
+
35
+ except ConnectionError as conn_err:
36
+ print(f"Connection error occurred for : {conn_err}")
37
+
38
+ except Timeout as timeout_err:
39
+ print(f"Request timed out: {timeout_err}")
40
+
41
+ except RequestException as req_err:
42
+ print(f"General error occurred: {req_err}")
43
+
20
44
 
21
45
  def _get(self, endpoint, params=None):
22
46
 
@@ -45,20 +69,50 @@ class SpireClient():
45
69
  return response.json()
46
70
 
47
71
  def _post(self, endpoint, data=None, json=None):
72
+ """
73
+ Send a POST request to the Spire API.
74
+
75
+ Args:
76
+ endpoint (str): The relative API endpoint (e.g., 'sales/orders').
77
+ data (dict, optional): Data to send in the body of the request.
78
+ json (dict, optional): JSON data to send in the body of the request.
79
+
80
+ Returns:
81
+ dict: A dictionary containing the response status code, URL, content, and headers.
82
+ """
48
83
  url = f"{self.base_url}/{endpoint.lstrip('/')}"
49
84
  response = self.session.post(url, data=data, json=json)
50
85
  return self._handle_response(response)
51
86
 
52
87
  def _put(self, endpoint, data=None, json=None):
88
+ """
89
+ Send a PUT request to the Spire API.
90
+
91
+ Args:
92
+ endpoint (str): The relative API endpoint (e.g., 'inventory/items/123').
93
+ data (dict, optional): Data to send in the body of the request.
94
+ json (dict, optional): JSON data to send in the body of the request.
95
+
96
+ Returns:
97
+ dict: A dictionary containing the response status code, URL, content, and headers.
98
+ """
53
99
  url = f"{self.base_url}/{endpoint.lstrip('/')}"
54
100
  response = self.session.put(url, data=data, json=json)
55
- response.raise_for_status()
56
- return response.json()
101
+ return self._handle_response(response)
57
102
 
58
103
  def _delete(self, endpoint):
104
+ """
105
+ Send a DELETE request to the Spire API.
106
+
107
+ Args:
108
+ endpoint (str): The relative API endpoint to delete (e.g., 'inventory/items/123').
109
+
110
+ Returns:
111
+ bool: True if the deletion was successful (status code 200, 202, or 204), False otherwise.
112
+ """
59
113
  url = f"{self.base_url}/{endpoint.lstrip('/')}"
60
114
  response = self.session.delete(url)
61
- return response.status_code == 200 or response.status_code == 204 or response.status_code == 202
115
+ return response.status_code in (200, 202, 204)
62
116
 
63
117
  def _handle_response(self, response):
64
118
 
@@ -106,12 +160,14 @@ class SpireClient():
106
160
  """
107
161
  collected = []
108
162
  current_start = start
109
-
163
+ remaining = limit
110
164
  while True:
165
+
166
+ current_limit = min(remaining, 1000)
111
167
  # Build the query params as a list of tuples to allow repeated keys like 'sort'
112
168
  params: List[Tuple[str, Any]] = [
113
169
  ("start", current_start),
114
- ("limit", min(limit, 1000))
170
+ ("limit", current_limit)
115
171
  ]
116
172
 
117
173
  if query:
@@ -142,10 +198,19 @@ class SpireClient():
142
198
  for item in items:
143
199
  collected.append(resource_cls.from_json(item, self))
144
200
 
145
- if not all or (current_start + limit >= count):
201
+ # Exit if:
202
+ # - 'all' is False and we reached the requested 'limit'
203
+ # - no more items are returned
204
+ if not all:
205
+ remaining -= len(items)
206
+ if remaining <= 0 or len(items) == 0:
207
+ break
208
+
209
+ # Exit if there are no more items available
210
+ if (current_start + current_limit) >= count or len(items) == 0:
146
211
  break
147
212
 
148
- current_start += limit
213
+ current_start += current_limit
149
214
 
150
215
  return collected
151
216
 
@@ -48,7 +48,7 @@ class ItemsClient():
48
48
 
49
49
  def create_item(self, item : 'InventoryItem') -> 'item':
50
50
  """
51
- Create a new Inventory Item in SPire.
51
+ Create a new Inventory Item in Spire.
52
52
 
53
53
  Sends a POST request to the Inventory/Items endpoint .
54
54
 
@@ -250,12 +250,9 @@ class salesOrder(APIResource[SalesOrder]):
250
250
 
251
251
  """
252
252
  response = self._client._post(f"/{self.endpoint}/{str(self.id)}/invoice")
253
- if response.get('status_code') == 201:
254
- location = response.get('headers').get('location')
255
- parsed_url = urlparse(location)
256
- path_segments = parsed_url.path.rstrip("/").split("/")
257
- id = path_segments[-1]
258
- return InvoiceClient(client=self._client).get_invoice(id)
253
+ if response.get('status_code') == 200:
254
+ data = response.get('content').get('invoice')
255
+ return salesOrder.from_json(json_data=data, client= self._client)
259
256
  else:
260
257
  error_message = response.get('content')
261
258
  raise CreateRequestError(self.endpoint, status_code=response.get('status_code'), error_message=error_message)
@@ -0,0 +1,23 @@
1
+ from .client import SpireClient
2
+ from .sales import OrdersClient, InvoiceClient
3
+ from .customers import CustomerClient
4
+ from .inventory import InventoryClient
5
+
6
+ class Spire:
7
+ def __init__(self, host : str, company : str, username : str, password : str):
8
+ """
9
+ Creates a spire Session
10
+
11
+ :param host (str): Spire Server host. eg: black-disk-5630.spirelan.com:10880
12
+ :param company (str): Spire company
13
+ :param username (str): Spire user username.
14
+ :param password (str): Spire user password.
15
+
16
+ """
17
+ self.client = SpireClient(host, company, username, password)
18
+ self.orders = OrdersClient(self.client)
19
+ self.invoices = InvoiceClient(self.client)
20
+ self.customers = CustomerClient(self.client)
21
+ self.inventory = InventoryClient(self.client)
22
+
23
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spyreapi
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: A robust and extensible Python client for interacting with the [Spire Business Software API](https://developer.spiresystems.com/reference). This client provides an object-oriented interface to get, create, update, delete, query, filter, sort, and manage various Spire modules such as Sales Orders, Invoices, Inventory Items, and more.
5
5
  Author-email: Sanjid Sharaf <sanjidsharaf1@gmail.com>
6
6
  License-Expression: MIT
@@ -48,14 +48,32 @@ pip install -r requirements.txt
48
48
 
49
49
  ## ⚙️ Configuration
50
50
 
51
- - Before using the client, set up your environment configuration:
51
+ - How to set up your spire client
52
52
 
53
- ### Add your Base URL to your spire server to a `.env` file
53
+ ### Find your Spire URL
54
54
 
55
- - In your project root, create a `.env` file to securely store your Spire configuration.
56
- - Add the following variable:
55
+ The base URL for the Spire API is the same url provided by Spire that you use to access Spire server and uses port 10880 as default:
56
+ Replace {spire-url} with the url provided by Spire.
57
57
 
58
- ```env
59
- BASE_URL = https://{your-spire-domain}/api/v2/companies/
58
+ - https://{spire-url}:10880/api/v2/
59
+
60
+ Spire Cloud
61
+ If you are using Spire cloud you do not need to specify a port. The base URL for API for Spire Cloud customers would be:
62
+
63
+ - https://{spire-cloud-url}/api/v2/
64
+
65
+ ### Set up your client with your credentials
66
+
67
+ ```python
68
+ from spyre import Spire
69
+ # host is your spire url and the port if applicable
70
+ client = Spire(host = 'your-spire-host', company = 'comapany-name' , username = 'username' , password = 'password' )
71
+
72
+ ```
73
+
74
+ ## Example : Updating the status of an inventory item
75
+ ```python
76
+ item = client.inventory.items.get_item(1101) # Gets item with id 1101
77
+ item.status = 1 # Use either item. or item.model. . item.model. will bring up all attributes
78
+ item.update()
60
79
  ```
61
- - Replace {your-spire-domain} with your actual Spire server's hostname or IP address.
@@ -5,7 +5,6 @@ pyproject.toml
5
5
  src/spyre/Exceptions.py
6
6
  src/spyre/__init__.py
7
7
  src/spyre/client.py
8
- src/spyre/config.py
9
8
  src/spyre/crm.py
10
9
  src/spyre/customers.py
11
10
  src/spyre/inventory.py
spyreapi-0.0.3/README.md DELETED
@@ -1,39 +0,0 @@
1
- # Spire API Python Client
2
-
3
- A robust and extensible Python client for interacting with the [Spire Business Software API](https://developer.spiresystems.com/reference). This client provides an object-oriented interface to get, create, update, delete, query, filter, sort, and manage various Spire modules such as Sales Orders, Invoices, Inventory Items, and more.
4
-
5
- ---
6
-
7
- ## ✨ Features
8
-
9
- - ✅ Object-oriented resource wrappers for each module (e.g., `salesOrder`, `invoice`, `item`)
10
- - 🔍 Full-text search via `q` parameter
11
- - 🔁 Pagination with `start` and `limit` support
12
- - 🧾 JSON-based advanced filtering (supports `$gt`, `$lt`, `$in`, `$or`, etc.)
13
- - ↕️ Multi-field sorting with ascending/descending control
14
- - 🔧 Clean abstraction layer for API endpoints
15
- - 📦 Powered by `pydantic` models for validation
16
-
17
- ---
18
-
19
- ## 📦 Installation
20
-
21
- ```bash
22
- pip install -r requirements.txt
23
- ```
24
-
25
- ---
26
-
27
- ## ⚙️ Configuration
28
-
29
- - Before using the client, set up your environment configuration:
30
-
31
- ### Add your Base URL to your spire server to a `.env` file
32
-
33
- - In your project root, create a `.env` file to securely store your Spire configuration.
34
- - Add the following variable:
35
-
36
- ```env
37
- BASE_URL = https://{your-spire-domain}/api/v2/companies/
38
- ```
39
- - Replace {your-spire-domain} with your actual Spire server's hostname or IP address.
@@ -1,8 +0,0 @@
1
- import os
2
- from dotenv import load_dotenv
3
-
4
- load_dotenv()
5
-
6
- BASE_URL = os.getenv("BASE_URL")
7
- if not BASE_URL:
8
- raise ValueError("BASE_URL environment variable is not set. Please set it before running the application.")
@@ -1,14 +0,0 @@
1
- from .client import SpireClient
2
- from .sales import OrdersClient, InvoiceClient
3
- from .customers import CustomerClient
4
- from .inventory import InventoryClient
5
-
6
- class Spire:
7
- def __init__(self, company : str, username : str, password : str):
8
- self.client = SpireClient(company, username, password)
9
- self.orders = OrdersClient(self.client)
10
- self.invoices = InvoiceClient(self.client)
11
- self.customers = CustomerClient(self.client)
12
- self.inventory = InventoryClient(self.client)
13
-
14
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes