Skip to content

Commit 179bf5e

Browse files
Merge branch 'master' of github.com:inventree/inventree-python
2 parents a125ba7 + d186356 commit 179bf5e

23 files changed

+343
-552
lines changed

.github/workflows/ci.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,21 @@ jobs:
88

99
env:
1010
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
11+
INVENTREE_SITE_URL: http://localhost:8000
1112
INVENTREE_DB_ENGINE: django.db.backends.sqlite3
1213
INVENTREE_DB_NAME: ../inventree_unit_test_db.sqlite3
1314
INVENTREE_MEDIA_ROOT: ../test_inventree_media
1415
INVENTREE_STATIC_ROOT: ../test_inventree_static
1516
INVENTREE_BACKUP_DIR: ../test_inventree_backup
17+
INVENTREE_COOKIE_SAMESITE: False
1618
INVENTREE_ADMIN_USER: testuser
1719
INVENTREE_ADMIN_PASSWORD: testpassword
1820
INVENTREE_ADMIN_EMAIL: [email protected]
1921
INVENTREE_PYTHON_TEST_SERVER: http://localhost:12345
2022
INVENTREE_PYTHON_TEST_USERNAME: testuser
2123
INVENTREE_PYTHON_TEST_PASSWORD: testpassword
24+
INVENTREE_DEBUG: True
25+
INVENTREE_LOG_LEVEL: DEBUG
2226

2327
strategy:
2428
max-parallel: 4
@@ -43,8 +47,8 @@ jobs:
4347
cd inventree_server
4448
invoke install
4549
invoke migrate
46-
invoke import-fixtures
47-
invoke server -a 127.0.0.1:12345 &
50+
invoke dev.import-fixtures
51+
invoke dev.server -a 0.0.0.0:12345 &
4852
invoke wait
4953
- name: Run Tests
5054
run: |

inventree/api.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class InvenTreeAPI(object):
2222
Basic class for performing Inventree API requests.
2323
"""
2424

25-
MIN_SUPPORTED_API_VERSION = 51
25+
MIN_SUPPORTED_API_VERSION = 206
2626

2727
@staticmethod
2828
def getMinApiVersion():
@@ -46,6 +46,7 @@ def __init__(self, host=None, **kwargs):
4646
token-name - Name of the token to use (default = 'inventree-python-client')
4747
use_token_auth - Use token authentication? (default = True)
4848
verbose - Print extra debug messages (default = False)
49+
strict - Enforce strict HTTPS certificate checking (default = True)
4950
timeout - Set timeout to use (in seconds). Default: 10
5051
proxies - Definition of proxies as a dict (defaults to an empty dict)
5152
@@ -66,6 +67,7 @@ def __init__(self, host=None, **kwargs):
6667
self.token_name = kwargs.get('token_name', os.environ.get('INVENTREE_API_TOKEN_NAME', 'inventree-python-client'))
6768
self.timeout = kwargs.get('timeout', os.environ.get('INVENTREE_API_TIMEOUT', 10))
6869
self.proxies = kwargs.get('proxies', dict())
70+
self.strict = bool(kwargs.get('strict', True))
6971

7072
self.use_token_auth = kwargs.get('use_token_auth', True)
7173
self.verbose = kwargs.get('verbose', False)
@@ -194,7 +196,12 @@ def testServer(self):
194196
logger.info("Checking InvenTree server connection...")
195197

196198
try:
197-
response = requests.get(self.api_url, timeout=self.timeout, proxies=self.proxies)
199+
response = requests.get(
200+
self.api_url,
201+
timeout=self.timeout,
202+
proxies=self.proxies,
203+
verify=self.strict
204+
)
198205
except requests.exceptions.ConnectionError as e:
199206
logger.fatal(f"Server connection error: {str(type(e))}")
200207
return False
@@ -326,6 +333,9 @@ def request(self, api_url, **kwargs):
326333
else:
327334
payload['json'] = data
328335

336+
# Enforce strict HTTPS certificate checking?
337+
payload['verify'] = self.strict
338+
329339
# Debug request information
330340
logger.debug("Sending Request:")
331341
logger.debug(f" - URL: {method} {api_url}")
@@ -578,13 +588,15 @@ def downloadFile(self, url, destination, overwrite=False, params=None, proxies=d
578588
auth = self.auth
579589

580590
with requests.get(
581-
fullurl,
582-
stream=True,
583-
auth=auth,
584-
headers=headers,
585-
params=params,
586-
timeout=self.timeout,
587-
proxies=self.proxies) as response:
591+
fullurl,
592+
stream=True,
593+
auth=auth,
594+
headers=headers,
595+
params=params,
596+
timeout=self.timeout,
597+
proxies=self.proxies,
598+
verify=self.strict,
599+
) as response:
588600

589601
# Error code
590602
if response.status_code >= 300:

inventree/base.py

Lines changed: 64 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
import json
44
import logging
5+
import requests
56
import os
6-
from typing import Type
77

88
from . import api as inventree_api
99

10-
INVENTREE_PYTHON_VERSION = "0.14.0"
10+
INVENTREE_PYTHON_VERSION = "0.17.4"
1111

1212

1313
logger = logging.getLogger('inventree')
@@ -19,6 +19,11 @@ class InventreeObject(object):
1919
# API URL (required) for the particular model type
2020
URL = ""
2121

22+
@classmethod
23+
def get_url(cls, api):
24+
"""Helper method to get the URL associated with this model."""
25+
return cls.URL
26+
2227
# Minimum server version for the particular model type
2328
MIN_API_VERSION = None
2429
MAX_API_VERSION = None
@@ -85,7 +90,9 @@ def __init__(self, api, pk=None, data=None):
8590
if pk <= 0:
8691
raise ValueError(f"Supplier <pk> value ({pk}) for {self.__class__} must be positive.")
8792

88-
self._url = f"{self.URL}/{pk}/"
93+
url = self.get_url(api)
94+
95+
self._url = f"{url}/{pk}/"
8996
self._api = api
9097

9198
if data is None:
@@ -225,10 +232,21 @@ def list(cls, api, **kwargs):
225232
else:
226233
url = cls.URL
227234

228-
response = api.get(url=url, params=kwargs)
235+
try:
236+
response = api.get(url=url, params=kwargs)
237+
except requests.exceptions.HTTPError as e:
238+
logger.error(f"Error during list request: {e}")
239+
# Return an empty list
240+
241+
raise_error = kwargs.get('raise_error', False)
242+
243+
if raise_error:
244+
raise e
245+
else:
246+
return []
229247

230248
if response is None:
231-
return None
249+
return []
232250

233251
items = []
234252

@@ -374,9 +392,6 @@ def bulkDelete(cls, api: inventree_api.InvenTreeAPI, items=None, filters=None):
374392
375393
"""
376394

377-
if api.api_version < 58:
378-
raise NotImplementedError("bulkDelete requires API version 58 or newer")
379-
380395
if not items and not filters:
381396
raise ValueError("Must supply either 'items' or 'filters' argument")
382397

@@ -395,14 +410,12 @@ def bulkDelete(cls, api: inventree_api.InvenTreeAPI, items=None, filters=None):
395410

396411

397412
class Attachment(BulkDeleteMixin, InventreeObject):
398-
"""
399-
Class representing a file attachment object
413+
"""Class representing a file attachment object."""
400414

401-
Multiple sub-classes exist, representing various types of attachment models in the database.
402-
"""
415+
URL = 'attachment/'
403416

404-
# Name of the primary key field of the InventreeObject the attachment will be attached to
405-
ATTACH_TO = None
417+
# Ref: https://github.com/inventree/InvenTree/pull/7420
418+
MIN_API_VERSION = 207
406419

407420
@classmethod
408421
def add_link(cls, api, link, comment="", **kwargs):
@@ -420,9 +433,6 @@ def add_link(cls, api, link, comment="", **kwargs):
420433
data["comment"] = comment
421434
data["link"] = link
422435

423-
if cls.ATTACH_TO not in kwargs:
424-
raise ValueError(f"Required argument '{cls.ATTACH_TO}' not supplied to add_link method")
425-
426436
if response := api.post(cls.URL, data):
427437
logger.info(f"Link attachment added to {cls.URL}")
428438
else:
@@ -446,18 +456,14 @@ def upload(cls, api, attachment, comment='', **kwargs):
446456
data = kwargs
447457
data['comment'] = comment
448458

449-
if cls.ATTACH_TO not in kwargs:
450-
raise ValueError(f"Required argument '{cls.ATTACH_TO}' not supplied to upload method")
451-
452459
if type(attachment) is str:
453460
if not os.path.exists(attachment):
454461
raise FileNotFoundError(f"Attachment file '{attachment}' does not exist")
455462

456463
# Load the file as an in-memory file object
457464
with open(attachment, 'rb') as fo:
458-
459465
response = api.post(
460-
cls.URL,
466+
Attachment.URL,
461467
data,
462468
files={
463469
'attachment': (os.path.basename(attachment), fo),
@@ -467,8 +473,9 @@ def upload(cls, api, attachment, comment='', **kwargs):
467473
else:
468474
# Assumes a StringIO or BytesIO like object
469475
name = getattr(attachment, 'name', 'filename')
476+
470477
response = api.post(
471-
cls.URL,
478+
Attachment.URL,
472479
data,
473480
files={
474481
'attachment': (name, attachment),
@@ -490,47 +497,44 @@ def download(self, destination, **kwargs):
490497
return self._api.downloadFile(self.attachment, destination, **kwargs)
491498

492499

493-
def AttachmentMixin(AttachmentSubClass: Type[Attachment]):
494-
class Mixin(Attachment):
495-
def getAttachments(self):
496-
return AttachmentSubClass.list(
497-
self._api,
498-
**{AttachmentSubClass.ATTACH_TO: self.pk},
499-
)
500+
class AttachmentMixin:
501+
"""Mixin class which allows a model class to interact with attachments."""
500502

501-
def uploadAttachment(self, attachment, comment=""):
502-
"""
503-
Upload an attachment (file) against this Object.
503+
def getAttachments(self):
504+
"""Return a list of attachments associated with this object."""
504505

505-
Args:
506-
attachment: Either a string (filename) or a file object
507-
comment: Attachment comment
508-
"""
506+
return Attachment.list(
507+
self._api,
508+
model_type=self.getModelType(),
509+
model_id=self.pk
510+
)
509511

510-
return AttachmentSubClass.upload(
511-
self._api,
512-
attachment,
513-
comment=comment,
514-
**{AttachmentSubClass.ATTACH_TO: self.pk},
515-
)
512+
def uploadAttachment(self, attachment, comment=""):
513+
"""Upload a file attachment against this model instance."""
514+
515+
return Attachment.upload(
516+
self._api,
517+
attachment,
518+
comment=comment,
519+
model_type=self.getModelType(),
520+
model_id=self.pk
521+
)
516522

517-
def addLinkAttachment(self, link, comment=""):
518-
"""
519-
Add an external link attachment against this Object.
520-
521-
Args:
522-
link: The link to attach
523-
comment: Attachment comment
524-
"""
525-
526-
return AttachmentSubClass.add_link(
527-
self._api,
528-
link,
529-
comment=comment,
530-
**{AttachmentSubClass.ATTACH_TO: self.pk},
531-
)
523+
def addLinkAttachment(self, link, comment=""):
524+
"""Add an external link attachment against this Object.
532525
533-
return Mixin
526+
Args:
527+
link: The link to attach
528+
comment: Attachment comment
529+
"""
530+
531+
return Attachment.add_link(
532+
self._api,
533+
link,
534+
comment=comment,
535+
model_type=self.getModelType(),
536+
model_id=self.pk
537+
)
534538

535539

536540
class MetadataMixin:
@@ -553,10 +557,6 @@ def getMetadata(self):
553557
"""Read model instance metadata"""
554558
if self._api:
555559

556-
if self._api.api_version < 49:
557-
logger.error("API version 49 or newer required to access instance metadata")
558-
return {}
559-
560560
response = self._api.get(self.metadata_url)
561561

562562
return response['metadata']
@@ -576,10 +576,6 @@ def setMetadata(self, data, overwrite=False):
576576

577577
if self._api:
578578

579-
if self._api.api_version < 49:
580-
logger.error("API version 49 or newer required to access instance metadata")
581-
return None
582-
583579
if overwrite:
584580
return self._api.put(
585581
self.metadata_url,
@@ -664,6 +660,7 @@ def _statusupdate(self, status: str, reload=True, data=None, **kwargs):
664660
if status not in [
665661
'complete',
666662
'cancel',
663+
'hold',
667664
'ship',
668665
'issue',
669666
'finish',

inventree/build.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,8 @@
44
import inventree.report
55

66

7-
class BuildAttachment(inventree.base.Attachment):
8-
"""Class representing an attachment against a Build object"""
9-
10-
URL = 'build/attachment'
11-
ATTACH_TO = 'build'
12-
13-
147
class Build(
15-
inventree.base.AttachmentMixin(BuildAttachment),
8+
inventree.base.AttachmentMixin,
169
inventree.base.StatusMixin,
1710
inventree.base.MetadataMixin,
1811
inventree.report.ReportPrintingMixin,
@@ -23,9 +16,13 @@ class Build(
2316
URL = 'build'
2417
MODEL_TYPE = 'build'
2518

26-
# Setup for Report mixin
27-
REPORTNAME = 'build'
28-
REPORTITEM = 'build'
19+
def issue(self):
20+
"""Mark this build as 'issued'."""
21+
return self._statusupdate(status='issue')
22+
23+
def hold(self):
24+
"""Mark this build as 'on hold'."""
25+
return self._statusupdate(status='hold')
2926

3027
def complete(
3128
self,

0 commit comments

Comments
 (0)