22
33import json
44import logging
5+ import requests
56import os
6- from typing import Type
77
88from . import api as inventree_api
99
10- INVENTREE_PYTHON_VERSION = "0.14.0 "
10+ INVENTREE_PYTHON_VERSION = "0.17.4 "
1111
1212
1313logger = 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
397412class 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
536540class 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' ,
0 commit comments