polyswarm-api 4.0.0__tar.gz → 4.2.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.
- {polyswarm_api-4.0.0/src/polyswarm_api.egg-info → polyswarm_api-4.2.0}/PKG-INFO +1 -1
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/pyproject.toml +2 -2
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/__init__.py +1 -1
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/aio/api.py +52 -5
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/aio/session.py +13 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/api.py +69 -4
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/resources.py +30 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/session.py +14 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0/src/polyswarm_api.egg-info}/PKG-INFO +1 -1
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/LICENSE +0 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/README.md +0 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/setup.cfg +0 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/aio/__init__.py +0 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/core.py +0 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/exceptions.py +0 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/settings.py +0 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api.egg-info/SOURCES.txt +0 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api.egg-info/dependency_links.txt +0 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api.egg-info/requires.txt +0 -0
- {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "polyswarm_api"
|
|
7
|
-
version = "4.
|
|
7
|
+
version = "4.2.0"
|
|
8
8
|
description = "Client library to simplify interacting with the PolySwarm consumer API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10,<4"
|
|
@@ -55,7 +55,7 @@ package-dir = { "" = "src" }
|
|
|
55
55
|
where = ["src"]
|
|
56
56
|
|
|
57
57
|
[tool.bumpversion]
|
|
58
|
-
current_version = "4.
|
|
58
|
+
current_version = "4.2.0"
|
|
59
59
|
commit = true
|
|
60
60
|
tag = false
|
|
61
61
|
sign_tags = true
|
|
@@ -767,6 +767,49 @@ class PolySwarmAsyncAPI:
|
|
|
767
767
|
async for item in self._paginate(resources.Tag.list(self)):
|
|
768
768
|
yield item
|
|
769
769
|
|
|
770
|
+
async def known_good_create(self, sha256, source, sha1=None, md5=None,
|
|
771
|
+
filename=None, mimetype=None, metadata=None):
|
|
772
|
+
"""
|
|
773
|
+
Record a hash as a known-good binary (internal-only). The first feed for a
|
|
774
|
+
sha256 creates the searchable reference; a different ``source`` for an
|
|
775
|
+
existing sha256 just records that feed.
|
|
776
|
+
|
|
777
|
+
:param sha256: The sha256 of the known-good binary.
|
|
778
|
+
:param source: The feed/source flagging this hash (becomes a metadata tool).
|
|
779
|
+
:param sha1: Optional sha1.
|
|
780
|
+
:param md5: Optional md5.
|
|
781
|
+
:param filename: Optional filename.
|
|
782
|
+
:param mimetype: Optional mimetype.
|
|
783
|
+
:param metadata: Optional dict of extra feed metadata.
|
|
784
|
+
:return: A KnownGood resource
|
|
785
|
+
"""
|
|
786
|
+
logger.info('Create known-good %s from %s', sha256, source)
|
|
787
|
+
return await self._single(resources.KnownGood.create(
|
|
788
|
+
self, sha256=sha256, source=source, sha1=sha1, md5=md5,
|
|
789
|
+
filename=filename, mimetype=mimetype, metadata=metadata,
|
|
790
|
+
community=self.community))
|
|
791
|
+
|
|
792
|
+
async def known_good_get(self, sha256):
|
|
793
|
+
"""
|
|
794
|
+
Fetch a known-good entry by sha256.
|
|
795
|
+
:param sha256: The sha256 to look up.
|
|
796
|
+
:return: A KnownGood resource
|
|
797
|
+
"""
|
|
798
|
+
logger.info('Get known-good %s', sha256)
|
|
799
|
+
return await self._single(resources.KnownGood.get(
|
|
800
|
+
self, sha256=sha256, community=self.community))
|
|
801
|
+
|
|
802
|
+
async def known_good_delete(self, sha256):
|
|
803
|
+
"""
|
|
804
|
+
Delete a known-good entry by sha256 (the same key used to retrieve it).
|
|
805
|
+
Leaves any existing instances intact; only future submissions of that
|
|
806
|
+
sha256 stop being short-circuited.
|
|
807
|
+
:param sha256: The sha256 of the known-good entry to delete.
|
|
808
|
+
:return: A KnownGood resource (the deletion result)
|
|
809
|
+
"""
|
|
810
|
+
logger.info('Delete known-good %s', sha256)
|
|
811
|
+
return await self._single(resources.KnownGood.delete(self, sha256=sha256))
|
|
812
|
+
|
|
770
813
|
async def family_create(self, name):
|
|
771
814
|
"""
|
|
772
815
|
Create a Family.
|
|
@@ -982,7 +1025,8 @@ class PolySwarmAsyncAPI:
|
|
|
982
1025
|
report_task = await self._single(resources.ReportLLMPostProcessing.create(self,
|
|
983
1026
|
instance_id=instance_id,
|
|
984
1027
|
cape_sandbox_task_id=cape_sandbox_task_id,
|
|
985
|
-
triage_sandbox_task_id=triage_sandbox_task_id
|
|
1028
|
+
triage_sandbox_task_id=triage_sandbox_task_id,
|
|
1029
|
+
community=self.community))
|
|
986
1030
|
return report_task
|
|
987
1031
|
|
|
988
1032
|
async def llm_report_get(self, report_task_id):
|
|
@@ -1608,8 +1652,11 @@ class PolySwarmAsyncAPI:
|
|
|
1608
1652
|
self, hash_.hash, hash_.hash_type, require_scan=require_scan,
|
|
1609
1653
|
),
|
|
1610
1654
|
)
|
|
1611
|
-
# exists_hash is a HEAD; ``result`` is the status code.
|
|
1612
|
-
#
|
|
1613
|
-
#
|
|
1614
|
-
|
|
1655
|
+
# exists_hash is a HEAD; ``result`` is the status code. The endpoint returns
|
|
1656
|
+
# 200 when the artifact is present and 204 when it is absent ("the request
|
|
1657
|
+
# worked, but there was no matching artifact, so nothing was returned"). Only
|
|
1658
|
+
# a 200 means the artifact exists — 204 (and 404) both mean "absent" → False.
|
|
1659
|
+
# NB: this is deliberately ``== 200``, not a ``// 100 == 2`` 2xx check, because
|
|
1660
|
+
# 204 is a successful 2xx that means the opposite of "exists".
|
|
1661
|
+
return int(result) == 200
|
|
1615
1662
|
|
|
@@ -148,6 +148,19 @@ class AsyncPolyswarmSession:
|
|
|
148
148
|
await response.aread()
|
|
149
149
|
_raise_for_status(response, request)
|
|
150
150
|
|
|
151
|
+
if response.status_code == 204:
|
|
152
|
+
# A 204 is the "no data / absent" signal on a download: the request
|
|
153
|
+
# succeeded but there is nothing to write ("the request worked, but
|
|
154
|
+
# there was no matching artifact, so nothing was returned"). The
|
|
155
|
+
# shared ``parse_response`` maps a parser-backed 204 to
|
|
156
|
+
# ``NoResultsException``; the streaming path bypasses ``parse_response``,
|
|
157
|
+
# so mirror that rule explicitly here. Without it, an absent artifact
|
|
158
|
+
# would be written out as a successful *empty* file instead of
|
|
159
|
+
# surfacing "no results".
|
|
160
|
+
raise exceptions.NoResultsException(
|
|
161
|
+
request, 'The request returned no results.',
|
|
162
|
+
)
|
|
163
|
+
|
|
151
164
|
pk = request.parser_kwargs or {}
|
|
152
165
|
handle, name, created = request.result_parser.open_destination(
|
|
153
166
|
pk.get('folder'), pk.get('handle'), pk.get('artifact_name'), response,
|
|
@@ -925,6 +925,67 @@ class PolyswarmAPI:
|
|
|
925
925
|
for item in self._paginate(resources.Tag.list(self)):
|
|
926
926
|
yield item
|
|
927
927
|
|
|
928
|
+
def known_good_create(
|
|
929
|
+
self,
|
|
930
|
+
sha256,
|
|
931
|
+
source,
|
|
932
|
+
sha1=None,
|
|
933
|
+
md5=None,
|
|
934
|
+
filename=None,
|
|
935
|
+
mimetype=None,
|
|
936
|
+
metadata=None,
|
|
937
|
+
):
|
|
938
|
+
"""
|
|
939
|
+
Record a hash as a known-good binary (internal-only). The first feed for a
|
|
940
|
+
sha256 creates the searchable reference; a different ``source`` for an
|
|
941
|
+
existing sha256 just records that feed.
|
|
942
|
+
|
|
943
|
+
:param sha256: The sha256 of the known-good binary.
|
|
944
|
+
:param source: The feed/source flagging this hash (becomes a metadata tool).
|
|
945
|
+
:param sha1: Optional sha1.
|
|
946
|
+
:param md5: Optional md5.
|
|
947
|
+
:param filename: Optional filename.
|
|
948
|
+
:param mimetype: Optional mimetype.
|
|
949
|
+
:param metadata: Optional dict of extra feed metadata.
|
|
950
|
+
:return: A KnownGood resource
|
|
951
|
+
"""
|
|
952
|
+
logger.info("Create known-good %s from %s", sha256, source)
|
|
953
|
+
return self._single(
|
|
954
|
+
resources.KnownGood.create(
|
|
955
|
+
self,
|
|
956
|
+
sha256=sha256,
|
|
957
|
+
source=source,
|
|
958
|
+
sha1=sha1,
|
|
959
|
+
md5=md5,
|
|
960
|
+
filename=filename,
|
|
961
|
+
mimetype=mimetype,
|
|
962
|
+
metadata=metadata,
|
|
963
|
+
community=self.community,
|
|
964
|
+
)
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
def known_good_get(self, sha256):
|
|
968
|
+
"""
|
|
969
|
+
Fetch a known-good entry by sha256.
|
|
970
|
+
:param sha256: The sha256 to look up.
|
|
971
|
+
:return: A KnownGood resource
|
|
972
|
+
"""
|
|
973
|
+
logger.info("Get known-good %s", sha256)
|
|
974
|
+
return self._single(
|
|
975
|
+
resources.KnownGood.get(self, sha256=sha256, community=self.community)
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
def known_good_delete(self, sha256):
|
|
979
|
+
"""
|
|
980
|
+
Delete a known-good entry by sha256 (the same key used to retrieve it).
|
|
981
|
+
Leaves any existing instances intact; only future submissions of that
|
|
982
|
+
sha256 stop being short-circuited.
|
|
983
|
+
:param sha256: The sha256 of the known-good entry to delete.
|
|
984
|
+
:return: A KnownGood resource (the deletion result)
|
|
985
|
+
"""
|
|
986
|
+
logger.info("Delete known-good %s", sha256)
|
|
987
|
+
return self._single(resources.KnownGood.delete(self, sha256=sha256))
|
|
988
|
+
|
|
928
989
|
def family_create(self, name):
|
|
929
990
|
"""
|
|
930
991
|
Create a Family.
|
|
@@ -1201,6 +1262,7 @@ class PolyswarmAPI:
|
|
|
1201
1262
|
instance_id=instance_id,
|
|
1202
1263
|
cape_sandbox_task_id=cape_sandbox_task_id,
|
|
1203
1264
|
triage_sandbox_task_id=triage_sandbox_task_id,
|
|
1265
|
+
community=self.community,
|
|
1204
1266
|
)
|
|
1205
1267
|
)
|
|
1206
1268
|
return report_task
|
|
@@ -1940,7 +2002,10 @@ class PolyswarmAPI:
|
|
|
1940
2002
|
require_scan=require_scan,
|
|
1941
2003
|
),
|
|
1942
2004
|
)
|
|
1943
|
-
# exists_hash is a HEAD; ``result`` is the status code.
|
|
1944
|
-
#
|
|
1945
|
-
#
|
|
1946
|
-
|
|
2005
|
+
# exists_hash is a HEAD; ``result`` is the status code. The endpoint returns
|
|
2006
|
+
# 200 when the artifact is present and 204 when it is absent ("the request
|
|
2007
|
+
# worked, but there was no matching artifact, so nothing was returned"). Only
|
|
2008
|
+
# a 200 means the artifact exists — 204 (and 404) both mean "absent" → False.
|
|
2009
|
+
# NB: this is deliberately ``== 200``, not a ``// 100 == 2`` 2xx check, because
|
|
2010
|
+
# 204 is a successful 2xx that means the opposite of "exists".
|
|
2011
|
+
return int(result) == 200
|
|
@@ -266,6 +266,19 @@ class ArtifactInstance(core.BaseJsonResource, core.Hashable):
|
|
|
266
266
|
metadata_json = content.get('metadata') or []
|
|
267
267
|
metadata = {metadata['tool']: metadata['tool_metadata'] for metadata in metadata_json}
|
|
268
268
|
self.metadata = Metadata(metadata, api)
|
|
269
|
+
# Flagging-feed metadata for a known-good binary: a list of
|
|
270
|
+
# {tool, tool_metadata, created, updated} entries (one per feed that
|
|
271
|
+
# flagged this sha256 as known-good), or None for a normal artifact.
|
|
272
|
+
# Optional + additive — absent on responses from older servers, so use
|
|
273
|
+
# .get() (older recorded responses parse to None, no behaviour change).
|
|
274
|
+
self.known_good = content.get('known_good')
|
|
275
|
+
self.known_good_sources = sorted(
|
|
276
|
+
{feed['tool'] for feed in self.known_good if feed.get('tool')}
|
|
277
|
+
) if self.known_good else []
|
|
278
|
+
# Friendly bounty-state NAME (e.g. 'KNOWN_GOOD' / 'SETTLED' / 'STORED'),
|
|
279
|
+
# additive alongside the numeric bounty_state. Optional — older servers
|
|
280
|
+
# omit it, so .get() yields None (no behaviour change).
|
|
281
|
+
self.state = content.get('state')
|
|
269
282
|
|
|
270
283
|
# ArtifactInstance fields
|
|
271
284
|
self.id = content.get('id')
|
|
@@ -859,6 +872,23 @@ class Tag(core.BaseJsonResource):
|
|
|
859
872
|
self.name = content.get('name')
|
|
860
873
|
|
|
861
874
|
|
|
875
|
+
class KnownGood(core.BaseJsonResource):
|
|
876
|
+
# sha256 is the natural unique key for a known-good entry, and it's the key
|
|
877
|
+
# for BOTH retrieve and delete — so RESOURCE_ID_KEYS=['sha256'] lets the base
|
|
878
|
+
# _get_params / _delete_params route it into the query string with no override.
|
|
879
|
+
RESOURCE_ENDPOINT = '/known-good'
|
|
880
|
+
RESOURCE_ID_KEYS = ['sha256']
|
|
881
|
+
|
|
882
|
+
def __init__(self, content, api=None):
|
|
883
|
+
super().__init__(content, api=api)
|
|
884
|
+
self.id = content.get('id')
|
|
885
|
+
self.sha256 = content.get('sha256')
|
|
886
|
+
self.artifact_instance_id = content.get('artifact_instance_id')
|
|
887
|
+
# feed names (metadata tools) that flagged this hash as known-good
|
|
888
|
+
self.sources = content.get('sources', [])
|
|
889
|
+
self.created = core.parse_isoformat(content.get('created'))
|
|
890
|
+
|
|
891
|
+
|
|
862
892
|
#####################################################################
|
|
863
893
|
# Nested Resources
|
|
864
894
|
#####################################################################
|
|
@@ -151,6 +151,20 @@ class PolyswarmSession:
|
|
|
151
151
|
response.read()
|
|
152
152
|
_raise_for_status(response, request)
|
|
153
153
|
|
|
154
|
+
if response.status_code == 204:
|
|
155
|
+
# A 204 is the "no data / absent" signal on a download: the request
|
|
156
|
+
# succeeded but there is nothing to write ("the request worked, but
|
|
157
|
+
# there was no matching artifact, so nothing was returned"). The
|
|
158
|
+
# shared ``parse_response`` maps a parser-backed 204 to
|
|
159
|
+
# ``NoResultsException``; the streaming path bypasses ``parse_response``,
|
|
160
|
+
# so mirror that rule explicitly here. Without it, an absent artifact
|
|
161
|
+
# would be written out as a successful *empty* file instead of
|
|
162
|
+
# surfacing "no results".
|
|
163
|
+
raise exceptions.NoResultsException(
|
|
164
|
+
request,
|
|
165
|
+
"The request returned no results.",
|
|
166
|
+
)
|
|
167
|
+
|
|
154
168
|
pk = request.parser_kwargs or {}
|
|
155
169
|
handle, name, created = request.result_parser.open_destination(
|
|
156
170
|
pk.get("folder"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|