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.
Files changed (20) hide show
  1. {polyswarm_api-4.0.0/src/polyswarm_api.egg-info → polyswarm_api-4.2.0}/PKG-INFO +1 -1
  2. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/pyproject.toml +2 -2
  3. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/__init__.py +1 -1
  4. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/aio/api.py +52 -5
  5. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/aio/session.py +13 -0
  6. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/api.py +69 -4
  7. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/resources.py +30 -0
  8. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/session.py +14 -0
  9. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0/src/polyswarm_api.egg-info}/PKG-INFO +1 -1
  10. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/LICENSE +0 -0
  11. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/README.md +0 -0
  12. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/setup.cfg +0 -0
  13. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/aio/__init__.py +0 -0
  14. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/core.py +0 -0
  15. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/exceptions.py +0 -0
  16. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api/settings.py +0 -0
  17. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api.egg-info/SOURCES.txt +0 -0
  18. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api.egg-info/dependency_links.txt +0 -0
  19. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api.egg-info/requires.txt +0 -0
  20. {polyswarm_api-4.0.0 → polyswarm_api-4.2.0}/src/polyswarm_api.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyswarm_api
3
- Version: 4.0.0
3
+ Version: 4.2.0
4
4
  Summary: Client library to simplify interacting with the PolySwarm consumer API
5
5
  Author-email: PolySwarm Developers <info@polyswarm.io>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "polyswarm_api"
7
- version = "4.0.0"
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.0.0"
58
+ current_version = "4.2.0"
59
59
  commit = true
60
60
  tag = false
61
61
  sign_tags = true
@@ -1,5 +1,5 @@
1
1
  # https://www.python.org/dev/peps/pep-0008/#module-level-dunder-names
2
- __version__ = '4.0.0'
2
+ __version__ = '4.2.0'
3
3
  __release_url__ = 'https://api.github.com/repos/polyswarm/polyswarm-api/releases/latest'
4
4
 
5
5
  from . import api
@@ -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. Any 2xx means the
1612
- # artifact is known — the endpoint returns 200 for present / 404 for absent,
1613
- # so a 2xx check is correct and not brittle to a non-200 success code.
1614
- return int(result) // 100 == 2
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. Any 2xx means the
1944
- # artifact is known — the endpoint returns 200 for present / 404 for absent,
1945
- # so a 2xx check is correct and not brittle to a non-200 success code.
1946
- return int(result) // 100 == 2
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"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyswarm_api
3
- Version: 4.0.0
3
+ Version: 4.2.0
4
4
  Summary: Client library to simplify interacting with the PolySwarm consumer API
5
5
  Author-email: PolySwarm Developers <info@polyswarm.io>
6
6
  License: MIT
File without changes
File without changes
File without changes