polyswarm-api 3.5.2__tar.gz → 3.7.0__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 (20) hide show
  1. {polyswarm-api-3.5.2/src/polyswarm_api.egg-info → polyswarm-api-3.7.0}/PKG-INFO +7 -3
  2. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/README.md +1 -1
  3. polyswarm-api-3.7.0/requirements.txt +7 -0
  4. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/setup.py +8 -6
  5. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/__init__.py +1 -1
  6. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/api.py +62 -21
  7. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/core.py +24 -29
  8. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/exceptions.py +1 -1
  9. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/resources.py +153 -66
  10. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0/src/polyswarm_api.egg-info}/PKG-INFO +7 -3
  11. polyswarm-api-3.7.0/src/polyswarm_api.egg-info/requires.txt +2 -0
  12. polyswarm-api-3.5.2/requirements.txt +0 -7
  13. polyswarm-api-3.5.2/src/polyswarm_api.egg-info/requires.txt +0 -6
  14. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/LICENSE +0 -0
  15. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/MANIFEST.in +0 -0
  16. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/setup.cfg +0 -0
  17. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api/settings.py +0 -0
  18. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api.egg-info/SOURCES.txt +0 -0
  19. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api.egg-info/dependency_links.txt +0 -0
  20. {polyswarm-api-3.5.2 → polyswarm-api-3.7.0}/src/polyswarm_api.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: polyswarm-api
3
- Version: 3.5.2
3
+ Version: 3.7.0
4
4
  Summary: Client library to simplify interacting with the PolySwarm consumer API
5
5
  Home-page: https://github.com/polyswarm/polyswarm-api
6
6
  Author: PolySwarm Developers
@@ -11,8 +11,12 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python :: 3.7
13
13
  Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
14
18
  Classifier: Programming Language :: Python :: Implementation :: PyPy
15
- Requires-Python: >3.5,<4
19
+ Requires-Python: >=3.7,<4
16
20
  Description-Content-Type: text/markdown
17
21
  License-File: LICENSE
18
22
 
@@ -21,7 +25,7 @@ License-File: LICENSE
21
25
  An interface to the PolySwarm Customer APIs.
22
26
  For an easy-to-use CLI tool, or as an example of how to use these APIs, please see [polyswarm-cli](https://github.com/polyswarm/polyswarm-cli)
23
27
 
24
- Supports Python 2.7 and greater.
28
+ Supports Python 3.7 and greater.
25
29
 
26
30
  ## Installation
27
31
 
@@ -3,7 +3,7 @@
3
3
  An interface to the PolySwarm Customer APIs.
4
4
  For an easy-to-use CLI tool, or as an example of how to use these APIs, please see [polyswarm-cli](https://github.com/polyswarm/polyswarm-cli)
5
5
 
6
- Supports Python 2.7 and greater.
6
+ Supports Python 3.7 and greater.
7
7
 
8
8
  ## Installation
9
9
 
@@ -0,0 +1,7 @@
1
+ requests>=2.31
2
+ pytest~=7.4
3
+ jsonschema~=3.0.2
4
+ responses==0.23.3
5
+ python-dateutil==2.9.0
6
+ vcrpy~=4.4; python_version == "3.7"
7
+ vcrpy~=5.0; python_version > "3.7"
@@ -9,7 +9,7 @@ with open('README.md', 'r') as readme:
9
9
 
10
10
  setup(
11
11
  name='polyswarm-api',
12
- version='3.5.2',
12
+ version='3.7.0',
13
13
  description='Client library to simplify interacting with the PolySwarm consumer API',
14
14
  long_description=long_description,
15
15
  long_description_content_type='text/markdown',
@@ -17,13 +17,11 @@ setup(
17
17
  author_email='info@polyswarm.io',
18
18
  url='https://github.com/polyswarm/polyswarm-api',
19
19
  license='MIT',
20
- python_requires='>3.5,<4',
20
+ python_requires='>=3.7,<4',
21
21
  install_requires=[
22
- 'requests~=2.22',
23
- 'future~=0.18',
24
- 'python-dateutil~=2.8',
22
+ 'requests~=2.31',
23
+ 'python-dateutil~=2.9',
25
24
  ],
26
- extras_require={':python_version < "3.0"': ['enum34==1.1.6']},
27
25
  include_package_data=True,
28
26
  packages=find_packages('src'),
29
27
  package_dir={'': 'src'},
@@ -33,6 +31,10 @@ setup(
33
31
  'Operating System :: OS Independent',
34
32
  'Programming Language :: Python :: 3.7',
35
33
  'Programming Language :: Python :: 3.8',
34
+ 'Programming Language :: Python :: 3.9',
35
+ 'Programming Language :: Python :: 3.10',
36
+ 'Programming Language :: Python :: 3.11',
37
+ 'Programming Language :: Python :: 3.12',
36
38
  'Programming Language :: Python :: Implementation :: PyPy',
37
39
  ]
38
40
  )
@@ -1,5 +1,5 @@
1
1
  # https://www.python.org/dev/peps/pep-0008/#module-level-dunder-names
2
- __version__ = '3.5.2'
2
+ __version__ = '3.7.0'
3
3
  __release_url__ = 'https://api.github.com/repos/polyswarm/polyswarm-api/releases/latest'
4
4
 
5
5
  from . import api
@@ -3,19 +3,14 @@ import time
3
3
 
4
4
  import polyswarm_api.core
5
5
 
6
- try:
7
- from urllib.parse import urlparse
8
- except ImportError:
9
- from urlparse import urlparse
10
-
11
- from future.utils import string_types
6
+ from urllib.parse import urlparse
12
7
 
13
8
  from polyswarm_api import exceptions, resources, settings
14
9
 
15
10
  logger = logging.getLogger(__name__)
16
11
 
17
12
 
18
- class PolyswarmAPI(object):
13
+ class PolyswarmAPI:
19
14
  """A synchronous interface to the public and private PolySwarm APIs."""
20
15
 
21
16
  def __init__(self, key, uri=None, community=None, timeout=None, verify=True, **kwargs):
@@ -35,6 +30,11 @@ class PolyswarmAPI(object):
35
30
  self.session = polyswarm_api.core.PolyswarmSession(key, retries=settings.DEFAULT_RETRIES, verify=verify, **kwargs)
36
31
  self._engines = None
37
32
 
33
+ def __repr__(self):
34
+ clsname = f'{self.__class__.__module__}.{self.__class__.__name__}'
35
+ attrs = f'uri={self.uri!r}, community={self.community!r}, timeout={self.timeout!r}'
36
+ return f'<{clsname}({attrs}) at 0x{id(self):x}>'
37
+
38
38
  @property
39
39
  def engines(self):
40
40
  if not self._engines:
@@ -44,11 +44,11 @@ class PolyswarmAPI(object):
44
44
 
45
45
  def refresh_engine_cache(self):
46
46
  """
47
- Rrefresh the cached engine listing
47
+ Refresh the cached engine listing
48
48
  """
49
49
  engines = list(resources.Engine.list(self).result())
50
50
  if not engines:
51
- raise exceptions.InvalidValueException("Recieved empty engines listing")
51
+ raise exceptions.InvalidValueException("Received empty engines listing")
52
52
  self._engines = engines
53
53
 
54
54
  def wait_for(self, scan, timeout=settings.DEFAULT_SCAN_TIMEOUT):
@@ -66,8 +66,8 @@ class PolyswarmAPI(object):
66
66
  if scan_result.failed or scan_result.window_closed:
67
67
  return scan_result
68
68
  elif -1 < timeout < time.time() - start:
69
- raise exceptions.TimeoutException('Timed out waiting for scan {} to finish. Please try again.'
70
- .format(scan))
69
+ raise exceptions.TimeoutException(
70
+ f'Timed out waiting for scan {scan} to finish. Please try again.')
71
71
  else:
72
72
  time.sleep(settings.POLL_FREQUENCY)
73
73
 
@@ -229,7 +229,7 @@ class PolyswarmAPI(object):
229
229
  if hasattr(artifact, 'read') and hasattr(artifact.read, '__call__'):
230
230
  artifact = resources.LocalArtifact.from_handle(self, artifact, artifact_name=artifact_name or '',
231
231
  artifact_type=artifact_type)
232
- elif isinstance(artifact, string_types):
232
+ elif isinstance(artifact, str):
233
233
  if artifact_type == resources.ArtifactType.FILE:
234
234
  artifact = resources.LocalArtifact.from_path(self, artifact, artifact_type=artifact_type,
235
235
  artifact_name=artifact_name)
@@ -245,7 +245,7 @@ class PolyswarmAPI(object):
245
245
  scan_config=scan_config,
246
246
  community=self.community).result()
247
247
  instance.upload_file(artifact)
248
- return resources.ArtifactInstance.update(self, id=instance.id).result()
248
+ return resources.ArtifactInstance.update(self, id=instance.id, community=self.community).result()
249
249
  else:
250
250
  raise exceptions.InvalidValueException('Artifacts should be a path to a file or a LocalArtifact instance')
251
251
 
@@ -284,7 +284,7 @@ class PolyswarmAPI(object):
284
284
  return resources.ArtifactInstance.rescan_id(self, scan, scan_config=scan_config).result()
285
285
 
286
286
  def _parse_rule(self, rule):
287
- if isinstance(rule, string_types):
287
+ if isinstance(rule, str):
288
288
  rule, rule_id = resources.YaraRuleset(dict(yara=rule), api=self), None
289
289
  elif isinstance(rule, (resources.YaraRuleset, int)):
290
290
  rule, rule_id = None, rule
@@ -699,7 +699,7 @@ class PolyswarmAPI(object):
699
699
  if hasattr(artifact, 'read') and hasattr(artifact.read, '__call__'):
700
700
  artifact = resources.LocalArtifact.from_handle(self, artifact, artifact_name=artifact_name or '',
701
701
  artifact_type=artifact_type)
702
- elif isinstance(artifact, string_types):
702
+ elif isinstance(artifact, str):
703
703
  if artifact_type == resources.ArtifactType.FILE:
704
704
  artifact = resources.LocalArtifact.from_path(self, artifact, artifact_type=artifact_type,
705
705
  artifact_name=artifact_name)
@@ -716,7 +716,7 @@ class PolyswarmAPI(object):
716
716
  vm_slug=vm_slug,
717
717
  network_enabled=network_enabled).result()
718
718
  task.upload_file(artifact)
719
- return resources.SandboxTask.update_file(self, id=task.id).result()
719
+ return resources.SandboxTask.update_file(self, id=task.id, community=self.community).result()
720
720
  else:
721
721
  raise exceptions.InvalidValueException(
722
722
  'Artifacts should be a path to a file or a LocalArtifact instance')
@@ -733,7 +733,7 @@ class PolyswarmAPI(object):
733
733
  browser=browser,
734
734
  network_enabled=True).result()
735
735
  task.upload_file(artifact)
736
- return resources.SandboxTask.update_file(self, id=task.id).result()
736
+ return resources.SandboxTask.update_file(self, id=task.id, community=self.community).result()
737
737
 
738
738
  def sandbox_providers(self):
739
739
  """
@@ -823,7 +823,48 @@ class PolyswarmAPI(object):
823
823
  logger.info('List events')
824
824
  return resources.Events.list(self, **kwargs).result()
825
825
 
826
- def __repr__(self):
827
- clsname = '{0.__module__}.{0.__name__}'.format(self.__class__)
828
- attrs = 'uri={0.uri!r}, community={0.community!r}, timeout={0.timeout!r}'.format(self)
829
- return '<{}({}) at 0x{:x}>'.format(clsname, attrs, id(self))
826
+ def report_create(self, **kwargs):
827
+ return resources.ReportTask.create(self, **kwargs).result()
828
+
829
+ def report_get(self, **kwargs):
830
+ return resources.ReportTask.get(self, **kwargs).result()
831
+
832
+ def report_download(self, report_id, folder):
833
+ report = resources.ReportTask.get(self, id=report_id).result()
834
+ result = report.download_report(folder=folder).result()
835
+ result.handle.close()
836
+ return result
837
+
838
+ def report_template_logo_download(self, template_id, folder):
839
+ report = resources.ReportTemplate.get(self, id=template_id).result()
840
+ result = report.download_logo(folder).result()
841
+ result.handle.close()
842
+ return result
843
+
844
+ def report_template_logo_delete(self, template_id):
845
+ report = resources.ReportTemplate.get(self, id=template_id).result()
846
+ result = report.delete_logo().result()
847
+ return result
848
+
849
+ def report_template_logo_upload(self, template_id, logo_file, content_tpe):
850
+ report = resources.ReportTemplate.get(self, id=template_id).result()
851
+ result = report.upload_logo(logo_file, content_tpe).result()
852
+ return result
853
+
854
+ def report_template_create(self, **kwargs):
855
+ return resources.ReportTemplate.create(self, **kwargs).result()
856
+
857
+ def report_template_update(self, template_id, **kwargs):
858
+ return resources.ReportTemplate.update(self, id=template_id, **kwargs).result()
859
+
860
+ def report_template_get(self, template_id):
861
+ return resources.ReportTemplate.get(self, id=template_id).result()
862
+
863
+ def report_template_delete(self, template_id):
864
+ return resources.ReportTemplate.delete(self, id=template_id).result()
865
+
866
+ def report_template_list(self, is_default=None):
867
+ params = {}
868
+ if is_default is not None:
869
+ params['is_default'] = is_default
870
+ return resources.ReportTemplate.list(self, **params).result()
@@ -3,15 +3,11 @@ import logging
3
3
  from copy import deepcopy
4
4
  from urllib3 import Retry
5
5
  from binascii import unhexlify
6
- try:
7
- from json.decoder import JSONDecodeError
8
- except ImportError:
9
- JSONDecodeError = ValueError
6
+ from json.decoder import JSONDecodeError
10
7
 
11
8
  import requests
12
9
  import datetime as dt
13
10
  from dateutil import parser
14
- from future.utils import raise_from
15
11
  from requests.adapters import HTTPAdapter
16
12
  from urllib3.exceptions import InsecureRequestWarning
17
13
 
@@ -22,7 +18,7 @@ logger = logging.getLogger(__name__)
22
18
 
23
19
  class PolyswarmSession(requests.Session):
24
20
  def __init__(self, key, retries, user_agent=settings.DEFAULT_USER_AGENT, verify=True, **kwargs):
25
- super(PolyswarmSession, self).__init__(**kwargs)
21
+ super().__init__(**kwargs)
26
22
  logger.debug('Creating PolyswarmHTTP instance')
27
23
  self.requests_retry_session(retries=retries)
28
24
 
@@ -118,13 +114,12 @@ class PolyswarmRequest(object):
118
114
 
119
115
  def _bad_status_message(self):
120
116
  request_parameters = json.dumps(self.request_parameters, indent=4, sort_keys=True, cls=RequestParamsEncoder)
121
- message = "Error when running the request:\n{}\n" \
122
- "Return code: {}\n" \
123
- "Message: {}".format(request_parameters,
124
- self.status_code,
125
- self._result)
117
+ message = f"Error when running the request:\n{request_parameters}\n" \
118
+ f"Return code: {self.status_code}\n" \
119
+ f"Message: {self._result}"
126
120
  if self.errors:
127
- message = '{}\nErrors:\n{}'.format(message, '\n'.join(str(error) for error in self.errors))
121
+ errors = '\n'.join(str(error) for error in self.errors)
122
+ message = f'{message}\nErrors:\n{errors}'
128
123
  return message
129
124
 
130
125
  def _extract_json_body(self, result):
@@ -146,10 +141,10 @@ class PolyswarmRequest(object):
146
141
  if self.status_code // 100 != 2:
147
142
  self._extract_json_body(result)
148
143
  if self.status_code == 429:
149
- message = '{} This may mean you need to purchase a ' \
144
+ message = f'{self._result} This may mean you need to purchase a ' \
150
145
  'larger package, or that you have exceeded ' \
151
146
  'rate limits. If you continue to have issues, ' \
152
- 'please contact us at info@polyswarm.io.'.format(self._result)
147
+ 'please contact us at info@polyswarm.io.'
153
148
  raise exceptions.UsageLimitsExceededException(self, message)
154
149
  elif self.status_code == 404:
155
150
  raise exceptions.NotFoundException(self, self._result)
@@ -185,10 +180,10 @@ class PolyswarmRequest(object):
185
180
  self._result = self.result_parser.parse_result(self.api_instance, result, **self.parser_kwargs)
186
181
  except JSONDecodeError as e:
187
182
  if self.status_code == 404:
188
- raise raise_from(exceptions.NotFoundException(self, 'The requested endpoint does not exist.'), e)
183
+ raise exceptions.NotFoundException(self, 'The requested endpoint does not exist.') from e
189
184
  else:
190
- err_msg = 'Server returned non-JSON response [{}]: {}'.format(self.status_code, result)
191
- raise raise_from(exceptions.RequestException(self, err_msg), e)
185
+ err_msg = f'Server returned non-JSON response [{self.status_code}]: {result}'
186
+ raise exceptions.RequestException(self, err_msg) from e
192
187
 
193
188
  def __iter__(self):
194
189
  return self.consume_results()
@@ -232,12 +227,12 @@ class PolyswarmRequest(object):
232
227
  ).execute()
233
228
 
234
229
 
235
- class BaseResource(object):
230
+ class BaseResource:
236
231
  def __init__(self, content, *args, **kwargs):
237
232
  # hack to behave as in python 3, signature should be
238
233
  # __init__(self, content, *args, api=None, **kwargs)
239
234
  api = kwargs.pop('api', None)
240
- super(BaseResource, self).__init__(*args, **kwargs)
235
+ super().__init__(*args, **kwargs)
241
236
  self.api = api
242
237
  self._content = content
243
238
 
@@ -252,13 +247,13 @@ class BaseJsonResource(BaseResource):
252
247
  RESOURCE_ID_KEYS = ['id']
253
248
 
254
249
  def __init__(self, content, *args, **kwargs):
255
- super(BaseJsonResource, self).__init__(content, *args, **kwargs)
250
+ super().__init__(content, *args, **kwargs)
256
251
  self.json = content
257
252
 
258
253
  def __int__(self):
259
254
  id_ = getattr(self, 'id', None)
260
255
  if id_ is None:
261
- raise TypeError('Resource {} does not have an id and can not be cast to int'.format(type(self).__name__))
256
+ raise TypeError(f'Resource {type(self).__name__} does not have an id and can not be cast to int')
262
257
  return int(id_)
263
258
 
264
259
  def _get(self, path, default=None, content=None):
@@ -271,16 +266,16 @@ class BaseJsonResource(BaseResource):
271
266
  try:
272
267
  for attribute in path.split('.'):
273
268
  if obj is None:
274
- raise KeyError('{} is None, can not resolve full path'.format(previous_attribute))
269
+ raise KeyError(f'{previous_attribute} is None, can not resolve full path')
275
270
  if attribute.endswith(']'):
276
271
  # handling the list case, e.g.: "root.list_attr[2]"
277
272
  attribute, _, index = attribute.rpartition('[')
278
273
  index = int(index.rstrip(']'))
279
274
  obj = obj[attribute]
280
275
  if obj is None:
281
- raise KeyError('{} is None, but is it supposed to be a list'.format(attribute))
276
+ raise KeyError(f'{attribute} is None, but is it supposed to be a list')
282
277
  elif not isinstance(obj, list):
283
- raise ValueError('Can not access index for {}, it is not a list.'.format(attribute))
278
+ raise ValueError(f'Can not access index for {attribute}, it is not a list.')
284
279
  else:
285
280
  obj = obj[index]
286
281
  else:
@@ -469,7 +464,7 @@ def is_valid_sha256(value):
469
464
  return is_hex(value)
470
465
 
471
466
 
472
- class Hashable(object):
467
+ class Hashable:
473
468
  SUPPORTED_HASH_TYPES = {
474
469
  'sha1': is_valid_sha1,
475
470
  'sha256': is_valid_sha256,
@@ -482,7 +477,7 @@ class Hashable(object):
482
477
  hash_value = kwargs.pop('hash_value', None)
483
478
  hash_type = kwargs.pop('hash_type', None)
484
479
  validate_hash = kwargs.pop('validate_hash', False)
485
- super(Hashable, self).__init__(*args, **kwargs)
480
+ super().__init__(*args, **kwargs)
486
481
 
487
482
  self._hash = hash_value.strip() if hash_value is not None else None
488
483
 
@@ -494,7 +489,7 @@ class Hashable(object):
494
489
  self._hash_type = self.resolve_hash_type()
495
490
 
496
491
  if self._hash_type is None:
497
- raise exceptions.InvalidValueException('Invalid hash provided: {}'.format(self._hash))
492
+ raise exceptions.InvalidValueException(f'Invalid hash provided: {self._hash}')
498
493
 
499
494
  if validate_hash:
500
495
  self.validate()
@@ -514,8 +509,8 @@ class Hashable(object):
514
509
  def validate(self):
515
510
  hash_type = self.resolve_hash_type()
516
511
  if self.hash_type != hash_type:
517
- raise exceptions.InvalidValueException('Detected hash type {}, got type {} for hash {}'
518
- .format(hash_type, self.hash_type, self.hash))
512
+ raise exceptions.InvalidValueException(
513
+ f'Detected hash type {hash_type}, got type {self.hash_type} for hash {self.hash}')
519
514
 
520
515
  def resolve_hash_type(self):
521
516
  for hash_type, validator in self.SUPPORTED_HASH_TYPES.items():
@@ -20,7 +20,7 @@ class TimeoutException(PolyswarmAPIException):
20
20
 
21
21
  class RequestException(PolyswarmException):
22
22
  def __init__(self, request, *args):
23
- super(PolyswarmException, self).__init__(*args)
23
+ super().__init__(*args)
24
24
  self.request = request
25
25
 
26
26
 
@@ -5,15 +5,9 @@ import functools
5
5
  import requests
6
6
  from enum import Enum
7
7
  from hashlib import sha256 as _sha256, sha1 as _sha1, md5 as _md5
8
+ from urllib.parse import urlparse
8
9
 
9
- try:
10
- from urllib.parse import urlparse
11
- except ImportError:
12
- from urlparse import urlparse
13
-
14
- from future.utils import raise_from, string_types
15
-
16
- # Windows might rase an OSError instead of an ImportError like this
10
+ # Windows might raise an OSError instead of an ImportError like this
17
11
  # OSError: [WinError 193] %1 is not a valid Win32 application
18
12
  try:
19
13
  import yara
@@ -33,7 +27,7 @@ class Engine(core.BaseJsonResource):
33
27
  RESOURCE_ENDPOINT = '/microengines'
34
28
 
35
29
  def __init__(self, content, api=None):
36
- super(Engine, self).__init__(content=content, api=api)
30
+ super().__init__(content=content, api=api)
37
31
  self.id = str(content['id'])
38
32
  self.name = content['name']
39
33
 
@@ -95,7 +89,7 @@ class Metadata(core.BaseJsonResource):
95
89
  KNOWN_KEYS = {'artifact', 'exiftool', 'hash', 'lief', 'pefile', 'scan', 'strings'}
96
90
 
97
91
  def __init__(self, content, api=None):
98
- super(Metadata, self).__init__(content=content, api=api)
92
+ super().__init__(content=content, api=api)
99
93
  self.created = core.parse_isoformat(self.artifact.get('created'))
100
94
 
101
95
  self.id = self._get('artifact.id')
@@ -144,7 +138,7 @@ class Metadata(core.BaseJsonResource):
144
138
  params.extend(('ips', v) for v in ips)
145
139
  params.extend(('urls', v) for v in urls)
146
140
  params.extend(('domains', v) for v in domains)
147
- super_params, json_params = super(Metadata, cls)._get_params(**kwargs)
141
+ super_params, json_params = super()._get_params(**kwargs)
148
142
  params.extend(super_params.items())
149
143
  return params, json_params
150
144
 
@@ -159,7 +153,7 @@ class IOC(core.BaseJsonResource):
159
153
  api,
160
154
  {
161
155
  'method': 'GET',
162
- 'url': '{}/{}/{}/{}'.format(api.uri, path, hash_type, hash_value),
156
+ 'url': f'{api.uri}/{path}/{hash_type}/{hash_value}',
163
157
  'params': {
164
158
  'hide_known_good': hide_known_good,
165
159
  'community': api.community,
@@ -183,7 +177,7 @@ class IOC(core.BaseJsonResource):
183
177
  api,
184
178
  {
185
179
  'method': 'GET',
186
- 'url': '{}/ioc/search'.format(api.uri),
180
+ 'url': f'{api.uri}/ioc/search',
187
181
  'params': params
188
182
  },
189
183
  result_parser=cls,
@@ -195,7 +189,7 @@ class IOC(core.BaseJsonResource):
195
189
  api,
196
190
  {
197
191
  'method': 'GET',
198
- 'url': '{}/ioc/known'.format(api.uri),
192
+ 'url': f'{api.uri}/ioc/known',
199
193
  'params': {
200
194
  'ip': ips,
201
195
  'domain': domains
@@ -210,7 +204,7 @@ class IOC(core.BaseJsonResource):
210
204
  api,
211
205
  {
212
206
  'method': 'POST',
213
- 'url': '{}/ioc/known'.format(api.uri),
207
+ 'url': f'{api.uri}/ioc/known',
214
208
  'json': {
215
209
  'type': type,
216
210
  'host': host,
@@ -227,7 +221,7 @@ class IOC(core.BaseJsonResource):
227
221
  api,
228
222
  {
229
223
  'method': 'POST',
230
- 'url': '{}/ioc/known'.format(api.uri),
224
+ 'url': f'{api.uri}/ioc/known',
231
225
  'json': {
232
226
  'type': type,
233
227
  'host': host,
@@ -244,7 +238,7 @@ class IOC(core.BaseJsonResource):
244
238
  api,
245
239
  {
246
240
  'method': 'PUT',
247
- 'url': '{}/ioc/known'.format(api.uri),
241
+ 'url': f'{api.uri}/ioc/known',
248
242
  'json': {
249
243
  'id': id,
250
244
  'type': type,
@@ -262,7 +256,7 @@ class IOC(core.BaseJsonResource):
262
256
  api,
263
257
  {
264
258
  'method': 'DELETE',
265
- 'url': '{}/ioc/known'.format(api.uri),
259
+ 'url': f'{api.uri}/ioc/known',
266
260
  'params': {
267
261
  'id': id
268
262
  }
@@ -275,8 +269,8 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
275
269
  RESOURCE_ENDPOINT = '/instance'
276
270
 
277
271
  def __init__(self, content, api=None):
278
- super(ArtifactInstance, self).__init__(content=content, api=api,
279
- hash_value=content['sha256'], hash_type='sha256')
272
+ super().__init__(content=content, api=api,
273
+ hash_value=content['sha256'], hash_type='sha256')
280
274
  # Artifact fields
281
275
  self.sha256 = content['sha256']
282
276
  self.artifact_id = content.get('artifact_id')
@@ -307,7 +301,10 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
307
301
  self.votes = [Vote(v, api=api, scanfile=self) for v in content.get('votes', [])]
308
302
  self.window_closed = content.get('window_closed')
309
303
  self.polyscore = float(content['polyscore']) if content.get('polyscore') is not None else None
310
- self.permalink = settings.DEFAULT_PERMALINK_BASE + '/' + str(self.hash) + '/' + str(self.id)
304
+ if content.get('permalink'):
305
+ self.permalink = content.get('permalink')
306
+ else:
307
+ self.permalink = settings.DEFAULT_PERMALINK_BASE + '/' + str(self.hash) + '/' + str(self.id)
311
308
 
312
309
  self._malicious_assertions = None
313
310
  self._benign_assertions = None
@@ -339,7 +336,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
339
336
  api,
340
337
  {
341
338
  'method': 'HEAD',
342
- 'url': '{}/search/hash/{}'.format(api.uri, hash_type),
339
+ 'url': f'{api.uri}/search/hash/{hash_type}',
343
340
  'params': {
344
341
  'hash': hash_value,
345
342
  'community': api.community,
@@ -353,7 +350,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
353
350
  api,
354
351
  {
355
352
  'method': 'GET',
356
- 'url': '{}/search/hash/{}'.format(api.uri, hash_type),
353
+ 'url': f'{api.uri}/search/hash/{hash_type}',
357
354
  'params': {
358
355
  'hash': hash_value,
359
356
  'community': api.community,
@@ -368,7 +365,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
368
365
  api,
369
366
  {
370
367
  'method': 'GET',
371
- 'url': '{}/search/url'.format(api.uri),
368
+ 'url': f'{api.uri}/search/url',
372
369
  'params': {
373
370
  'url': url,
374
371
  'community': api.community,
@@ -383,7 +380,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
383
380
  api,
384
381
  {
385
382
  'method': 'GET',
386
- 'url': '{}/search/instances'.format(api.uri),
383
+ 'url': f'{api.uri}/search/instances',
387
384
  'params': {
388
385
  'hash': hash_value,
389
386
  'community': api.community,
@@ -396,7 +393,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
396
393
  def submit(cls, api, artifact, artifact_name, artifact_type, scan_config=None):
397
394
  parameters = {
398
395
  'method': 'POST',
399
- 'url': '{}/consumer/submission/{}'.format(api.uri, api.community),
396
+ 'url': f'{api.uri}/consumer/submission/{api.community}',
400
397
  'files': {
401
398
  'file': (artifact_name, artifact),
402
399
  },
@@ -417,8 +414,8 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
417
414
  def rescan(cls, api, hash_value, hash_type, scan_config=None):
418
415
  parameters = {
419
416
  'method': 'POST',
420
- 'url': '{}/consumer/submission/{}/rescan/{}/{}'.format(api.uri, api.community, hash_type, hash_value),
421
- 'data': { 'community': api.community }
417
+ 'url': f'{api.uri}/consumer/submission/{api.community}/rescan/{hash_type}/{hash_value}',
418
+ 'data': {'community': api.community}
422
419
  }
423
420
  if scan_config:
424
421
  parameters['data']['scan-config'] = scan_config
@@ -432,7 +429,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
432
429
  def rescan_id(cls, api, submission_id, scan_config=None):
433
430
  parameters = {
434
431
  'method': 'POST',
435
- 'url': '{}/consumer/submission/{}/rescan/{}'.format(api.uri, api.community, int(submission_id)),
432
+ 'url': f'{api.uri}/consumer/submission/{api.community}/rescan/{int(submission_id)}',
436
433
  }
437
434
  if scan_config:
438
435
  parameters.setdefault('data', {})['scan-config'] = scan_config
@@ -448,7 +445,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
448
445
  api,
449
446
  {
450
447
  'method': 'GET',
451
- 'url': '{}/consumer/submission/{}/{}'.format(api.uri, api.community, int(submission_id)),
448
+ 'url': f'{api.uri}/consumer/submission/{api.community}/{int(submission_id)}',
452
449
  },
453
450
  result_parser=cls,
454
451
  ).execute()
@@ -457,7 +454,7 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
457
454
  def metadata_rerun(cls, api, hashes, analyses=None, skip_es=None):
458
455
  parameters = {
459
456
  'method': 'POST',
460
- 'url': '{}/consumer/metadata'.format(api.uri),
457
+ 'url': f'{api.uri}/consumer/metadata',
461
458
  'json': {'hashes': hashes},
462
459
  }
463
460
  if analyses:
@@ -496,7 +493,7 @@ class ArtifactArchive(core.BaseJsonResource):
496
493
  RESOURCE_ENDPOINT = '/consumer/download/stream'
497
494
 
498
495
  def __init__(self, content, api=None):
499
- super(ArtifactArchive, self).__init__(content=content, api=api)
496
+ super().__init__(content=content, api=api)
500
497
  self.id = content['id']
501
498
  self.community = content['community']
502
499
  self.created = core.parse_isoformat(content['created'])
@@ -507,7 +504,7 @@ class AssertionsJob(core.BaseJsonResource):
507
504
  RESOURCE_ENDPOINT = '/consumer/assertions-job'
508
505
 
509
506
  def __init__(self, content, api=None):
510
- super(AssertionsJob, self).__init__(content=content, api=api)
507
+ super().__init__(content=content, api=api)
511
508
  self.id = content['id']
512
509
  self.engine_id = content['engine_id']
513
510
  self.created = core.parse_isoformat(content['created'])
@@ -527,7 +524,7 @@ class VotesJob(core.BaseJsonResource):
527
524
  RESOURCE_ENDPOINT = '/consumer/votes-job'
528
525
 
529
526
  def __init__(self, content, api=None):
530
- super(VotesJob, self).__init__(content=content, api=api)
527
+ super().__init__(content=content, api=api)
531
528
  self.id = content['id']
532
529
  self.engine_id = content['engine_id']
533
530
  self.created = core.parse_isoformat(content['created'])
@@ -578,7 +575,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
578
575
  raise exceptions.InvalidValueException('At least one of path or handle must be defined.')
579
576
 
580
577
  # initialize super classes and default values
581
- super(LocalArtifact, self).__init__(response, api=api, hash_type='sha256')
578
+ super().__init__(response, api=api, hash_type='sha256')
582
579
  self.sha256 = None
583
580
  self.sha1 = None
584
581
  self.md5 = None
@@ -611,13 +608,8 @@ class LocalArtifact(core.BaseResource, core.Hashable):
611
608
  remove_on_error = False
612
609
  try:
613
610
  if folder:
614
- # TODO: this should be replaced with os.makedirs(path, exist_ok=True)
615
- # once we drop support to python 2.7
616
611
  if not os.path.exists(folder):
617
- try:
618
- os.makedirs(folder)
619
- except FileExistsError:
620
- pass
612
+ os.makedirs(folder, exist_ok=True)
621
613
  remove_on_error = True
622
614
  self.handle = open(os.path.join(folder, self.artifact_name), mode='wb+', **kwargs)
623
615
  else:
@@ -649,7 +641,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
649
641
  api,
650
642
  {
651
643
  'method': 'GET',
652
- 'url': '{}/consumer/download/{}/{}'.format(api.uri, hash_type, hash_value),
644
+ 'url': f'{api.uri}/consumer/download/{hash_type}/{hash_value}',
653
645
  'stream': True,
654
646
  'params': { 'community': api.community },
655
647
  },
@@ -665,7 +657,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
665
657
  api,
666
658
  {
667
659
  'method': 'GET',
668
- 'url': '{}/instance/download'.format(api.uri),
660
+ 'url': f'{api.uri}/instance/download',
669
661
  'stream': True,
670
662
  'params': {'instance_id': instance_id},
671
663
  },
@@ -726,7 +718,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
726
718
 
727
719
  @classmethod
728
720
  def from_path(cls, api, path, artifact_type=None, analyze=False, artifact_name=None, **kwargs):
729
- if not isinstance(path, string_types):
721
+ if not isinstance(path, str):
730
722
  raise exceptions.InvalidValueException('Path should be a string')
731
723
  artifact_name = artifact_name or os.path.basename(path)
732
724
  handle = open(path, mode='rb', **kwargs)
@@ -736,7 +728,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
736
728
 
737
729
  @classmethod
738
730
  def from_content(cls, api, content, artifact_name=None, artifact_type=None, analyze=False):
739
- if isinstance(content, string_types):
731
+ if isinstance(content, str):
740
732
  content = content.encode("utf8")
741
733
  handle = io.BytesIO(content)
742
734
  # create the LocalHandle with the given handle and don't write anything to it
@@ -746,7 +738,7 @@ class LocalArtifact(core.BaseResource, core.Hashable):
746
738
  @property
747
739
  def hash(self):
748
740
  self.analyze_artifact()
749
- return super(LocalArtifact, self).hash
741
+ return super().hash
750
742
 
751
743
  def analyze_artifact(self, force=False):
752
744
  if not self.analyzed or force:
@@ -773,7 +765,7 @@ class YaraRuleset(core.BaseJsonResource):
773
765
  RESOURCE_ENDPOINT = '/hunt/rule'
774
766
 
775
767
  def __init__(self, content, api=None):
776
- super(YaraRuleset, self).__init__(content, api=api)
768
+ super().__init__(content, api=api)
777
769
  self.id = content.get('id')
778
770
  self.livescan_id = content.get('livescan_id')
779
771
  self.livescan_created = content.get('livescan_created')
@@ -793,7 +785,7 @@ class LiveHuntResult(core.BaseJsonResource):
793
785
  RESOURCE_ENDPOINT = '/hunt/live'
794
786
 
795
787
  def __init__(self, content, api=None):
796
- super(LiveHuntResult, self).__init__(content=content, api=api)
788
+ super().__init__(content=content, api=api)
797
789
  self.id = content['id']
798
790
  self.livescan_id = content['livescan_id']
799
791
  self.instance_id = content['instance_id']
@@ -817,7 +809,7 @@ class HistoricalHunt(core.BaseJsonResource):
817
809
  RESOURCE_ENDPOINT = '/hunt/historical'
818
810
 
819
811
  def __init__(self, content, api=None):
820
- super(HistoricalHunt, self).__init__(content=content, api=api)
812
+ super().__init__(content=content, api=api)
821
813
  # active only present for live hunts
822
814
  self.id = content['id']
823
815
  self.created = core.parse_isoformat(content['created'])
@@ -839,7 +831,7 @@ class HistoricalHuntResult(core.BaseJsonResource):
839
831
  RESOURCE_ENDPOINT = '/hunt/historical/results'
840
832
 
841
833
  def __init__(self, content, api=None):
842
- super(HistoricalHuntResult, self).__init__(content=content, api=api)
834
+ super().__init__(content=content, api=api)
843
835
  self.id = content['id']
844
836
  self.historicalscan_id = content['historicalscan_id']
845
837
  self.instance_id = content['instance_id']
@@ -853,6 +845,7 @@ class HistoricalHuntResult(core.BaseJsonResource):
853
845
  self.download_url = content.get('download_url')
854
846
  self.community = content.get('community')
855
847
 
848
+
856
849
  class HistoricalHuntResultList(HistoricalHuntResult):
857
850
  RESOURCE_ENDPOINT = '/hunt/historical/results/list'
858
851
 
@@ -862,7 +855,7 @@ class TagLink(core.BaseJsonResource):
862
855
  RESOURCE_ID_KEYS = ['hash']
863
856
 
864
857
  def __init__(self, content, api=None):
865
- super(TagLink, self).__init__(content, api=api)
858
+ super().__init__(content, api=api)
866
859
  self.id = content.get('id')
867
860
  self.sha256 = content.get('sha256')
868
861
  self.created = core.parse_isoformat(content.get('created'))
@@ -889,7 +882,7 @@ class MalwareFamily(core.BaseJsonResource):
889
882
  RESOURCE_ID_KEYS = ['name']
890
883
 
891
884
  def __init__(self, content, api=None):
892
- super(MalwareFamily, self).__init__(content, api=api)
885
+ super().__init__(content, api=api)
893
886
  self.id = content.get('id')
894
887
  self.created = core.parse_isoformat(content.get('created'))
895
888
  self.updated = core.parse_isoformat(content.get('updated'))
@@ -902,7 +895,7 @@ class Tag(core.BaseJsonResource):
902
895
  RESOURCE_ID_KEYS = ['name']
903
896
 
904
897
  def __init__(self, content, api=None):
905
- super(Tag, self).__init__(content, api=api)
898
+ super().__init__(content, api=api)
906
899
  self.id = content.get('id')
907
900
  self.created = core.parse_isoformat(content.get('created'))
908
901
  self.updated = core.parse_isoformat(content.get('updated'))
@@ -916,7 +909,7 @@ class Tag(core.BaseJsonResource):
916
909
 
917
910
  class Assertion(core.BaseJsonResource):
918
911
  def __init__(self, content, api=None, scanfile=None):
919
- super(Assertion, self).__init__(content, api=api)
912
+ super().__init__(content, api=api)
920
913
  self.scanfile = scanfile
921
914
  self.author = content['author']
922
915
  self.author_name = content['author_name']
@@ -933,7 +926,7 @@ class Assertion(core.BaseJsonResource):
933
926
 
934
927
  class Vote(core.BaseJsonResource):
935
928
  def __init__(self, content, api=None, scanfile=None):
936
- super(Vote, self).__init__(content, api=api)
929
+ super().__init__(content, api=api)
937
930
  self.scanfile = scanfile
938
931
  self.arbiter = content['arbiter']
939
932
  self.vote = content['vote']
@@ -958,9 +951,8 @@ class ArtifactType(Enum):
958
951
  try:
959
952
  return ArtifactType[value.upper()]
960
953
  except Exception as e:
961
- raise raise_from(
962
- exceptions.InvalidValueException(
963
- 'Unable to get the artifact type from the provided value {}'.format(value)), e)
954
+ raise exceptions.InvalidValueException(
955
+ f'Unable to get the artifact type from the provided value {value}') from e
964
956
 
965
957
  @staticmethod
966
958
  def to_string(artifact_type):
@@ -981,7 +973,7 @@ class ArtifactType(Enum):
981
973
 
982
974
  class Hash(core.Hashable):
983
975
  def __init__(self, hash_, hash_type=None, validate_hash=True):
984
- super(Hash, self).__init__(hash_value=hash_, hash_type=hash_type, validate_hash=validate_hash)
976
+ super().__init__(hash_value=hash_, hash_type=hash_type, validate_hash=validate_hash)
985
977
 
986
978
  @classmethod
987
979
  def from_hashable(cls, hash_, hash_type=None):
@@ -995,8 +987,8 @@ class Hash(core.Hashable):
995
987
  """
996
988
  if issubclass(type(hash_), core.Hashable):
997
989
  if hash_type and hash_.hash_type != hash_type:
998
- raise exceptions.InvalidValueException('Detected hash type {}, got {} for hashable {}'
999
- .format(hash_.hash_type, hash_type, hash_.hash))
990
+ raise exceptions.InvalidValueException(
991
+ f'Detected hash type {hash_.hash_type}, got {hash_type} for hashable {hash_.hash}')
1000
992
  return Hash(hash_.hash, hash_type=hash_type)
1001
993
  return Hash(hash_, hash_type=hash_type)
1002
994
 
@@ -1007,14 +999,14 @@ class Hash(core.Hashable):
1007
999
  return self.hash
1008
1000
 
1009
1001
  def __repr__(self):
1010
- return "{}={}".format(self.hash_type, self.hash)
1002
+ return f"{self.hash_type}={self.hash}"
1011
1003
 
1012
1004
 
1013
1005
  class SandboxTask(core.BaseJsonResource):
1014
1006
  RESOURCE_ENDPOINT = '/sandbox/sandboxtask'
1015
1007
 
1016
1008
  def __init__(self, content, api=None):
1017
- super(SandboxTask, self).__init__(content, api=api)
1009
+ super().__init__(content, api=api)
1018
1010
  self.id = content['id']
1019
1011
  self.community = content['community']
1020
1012
  self.sandbox = content['sandbox']
@@ -1080,7 +1072,7 @@ class SandboxTask(core.BaseJsonResource):
1080
1072
 
1081
1073
  class SandboxArtifact(core.BaseJsonResource):
1082
1074
  def __init__(self, content, api=None):
1083
- super(SandboxArtifact, self).__init__(content, api=api)
1075
+ super().__init__(content, api=api)
1084
1076
  self.created = content['created']
1085
1077
  self.id = content['id']
1086
1078
  self.instance_id = content['instance_id']
@@ -1094,7 +1086,7 @@ class SandboxProvider(core.BaseJsonResource):
1094
1086
  RESOURCE_ENDPOINT = "/sandbox/provider"
1095
1087
 
1096
1088
  def __init__(self, content, api=None):
1097
- super(SandboxProvider, self).__init__(content, api=api)
1089
+ super().__init__(content, api=api)
1098
1090
  self.slug = content['slug']
1099
1091
  self.name = content['name']
1100
1092
  self.tool = content['tool']
@@ -1110,9 +1102,104 @@ class Events(core.BaseJsonResource):
1110
1102
  RESOURCE_ENDPOINT = "/activity"
1111
1103
 
1112
1104
  def __init__(self, content, api=None):
1113
- super(Events, self).__init__(content, api=api)
1105
+ super().__init__(content, api=api)
1114
1106
  self.event_timestamp = content['event_timestamp']
1115
1107
  self.event_type = content['event_type']
1116
1108
  self.source = content['source']
1117
1109
  self.team_account_id = content['team_account_id']
1118
1110
  self.user_account_id = content['user_account_id']
1111
+
1112
+
1113
+ class ReportTask(core.BaseJsonResource):
1114
+ RESOURCE_ENDPOINT = "/reports"
1115
+
1116
+ def __init__(self, content, api=None):
1117
+ super().__init__(content, api=api)
1118
+ self.id = content['id']
1119
+ self.type = content['type']
1120
+ self.format = content['format']
1121
+ self.state = content['state']
1122
+ self.community = content['community']
1123
+ self.created = content['created']
1124
+ self.template_id = content.get('template_id')
1125
+ self.template_metadata = content.get('template_metadata', {})
1126
+ self.sandbox_task_id = content.get('sandbox_task_id')
1127
+ self.instance_id = content.get('instance_id')
1128
+ self.url = content['url']
1129
+
1130
+ def download_report(self, folder=None):
1131
+ """ This method is special, in that it is simply for downloading from S3 """
1132
+ return core.PolyswarmRequest(
1133
+ self.api,
1134
+ {
1135
+ 'method': 'GET',
1136
+ 'url': self.url,
1137
+ 'stream': True,
1138
+ 'headers': {'Authorization': None}
1139
+ },
1140
+ result_parser=LocalArtifact,
1141
+ folder=folder,
1142
+ ).execute()
1143
+
1144
+
1145
+ class ReportTemplate(core.BaseJsonResource):
1146
+ RESOURCE_ENDPOINT = "/reports/templates"
1147
+
1148
+ def __init__(self, content, api=None):
1149
+ super().__init__(content, api=api)
1150
+ if content:
1151
+ self.id = content['id']
1152
+ self.created = content['created']
1153
+ self.template_name = content['template_name']
1154
+ self.includes = content.get('includes')
1155
+ self.primary_color = content.get('primary_color')
1156
+ self.footer_text = content.get('footer_text')
1157
+ self.last_page_text = content.get('last_page_text')
1158
+ self.is_default = content.get('is_default', False)
1159
+ self.logo_content_length = content.get('logo_content_length')
1160
+ self.logo_url = f"{self.api.uri}/reports/templates/logo?id={self.id}"
1161
+ self.logo_content_type = content.get('logo_content_type')
1162
+ self.logo_height = content.get('logo_height')
1163
+ self.logo_width = content.get('logo_width')
1164
+
1165
+ def download_logo(self, folder):
1166
+ return core.PolyswarmRequest(
1167
+ self.api,
1168
+ {
1169
+ 'method': 'GET',
1170
+ 'url': self.logo_url,
1171
+ },
1172
+ result_parser=LocalArtifact,
1173
+ folder=folder,
1174
+ ).execute()
1175
+
1176
+ def delete_logo(self):
1177
+ return core.PolyswarmRequest(
1178
+ self.api,
1179
+ {
1180
+ 'method': 'DELETE',
1181
+ 'url': self.logo_url,
1182
+ },
1183
+ ).execute()
1184
+
1185
+ def upload_logo(self, logo_file, content_tpe):
1186
+ if not logo_file:
1187
+ raise exceptions.InvalidValueException('A local file must be provided in order to upload')
1188
+ logo_file.seek(0, io.SEEK_END)
1189
+ length = logo_file.tell()
1190
+ logo_file.seek(0)
1191
+ if not length:
1192
+ raise exceptions.InvalidValueException('Empty file')
1193
+ # r = requests.put(self.upload_url, data=logo_file, **kwargs)
1194
+ # r.raise_for_status()
1195
+ # return r
1196
+ return core.PolyswarmRequest(
1197
+ self.api,
1198
+ {
1199
+ 'method': 'PUT',
1200
+ 'url': f'{self.api.uri}/reports/templates/logo?id={self.id}',
1201
+ 'data': logo_file,
1202
+ 'headers': {'Content-Type': content_tpe}
1203
+ },
1204
+ result_parser=self.__class__
1205
+ ).execute()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: polyswarm-api
3
- Version: 3.5.2
3
+ Version: 3.7.0
4
4
  Summary: Client library to simplify interacting with the PolySwarm consumer API
5
5
  Home-page: https://github.com/polyswarm/polyswarm-api
6
6
  Author: PolySwarm Developers
@@ -11,8 +11,12 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python :: 3.7
13
13
  Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
14
18
  Classifier: Programming Language :: Python :: Implementation :: PyPy
15
- Requires-Python: >3.5,<4
19
+ Requires-Python: >=3.7,<4
16
20
  Description-Content-Type: text/markdown
17
21
  License-File: LICENSE
18
22
 
@@ -21,7 +25,7 @@ License-File: LICENSE
21
25
  An interface to the PolySwarm Customer APIs.
22
26
  For an easy-to-use CLI tool, or as an example of how to use these APIs, please see [polyswarm-cli](https://github.com/polyswarm/polyswarm-cli)
23
27
 
24
- Supports Python 2.7 and greater.
28
+ Supports Python 3.7 and greater.
25
29
 
26
30
  ## Installation
27
31
 
@@ -0,0 +1,2 @@
1
+ requests~=2.31
2
+ python-dateutil~=2.9
@@ -1,7 +0,0 @@
1
- requests>=2.20
2
- pytest~=7.2
3
- jsonschema~=3.0.2
4
- responses==0.10.6
5
- python-dateutil==2.8.1
6
- future==0.18.2
7
- vcrpy~=5.0
@@ -1,6 +0,0 @@
1
- requests~=2.22
2
- future~=0.18
3
- python-dateutil~=2.8
4
-
5
- [:python_version < "3.0"]
6
- enum34==1.1.6
File without changes
File without changes
File without changes