greynoise 3.0.2__tar.gz → 3.0.3__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.
- {greynoise-3.0.2 → greynoise-3.0.3}/.bumpversion.cfg +1 -1
- {greynoise-3.0.2 → greynoise-3.0.3}/CHANGELOG.rst +10 -1
- {greynoise-3.0.2 → greynoise-3.0.3}/PKG-INFO +1 -1
- {greynoise-3.0.2 → greynoise-3.0.3}/docs/source/conf.py +1 -1
- {greynoise-3.0.2 → greynoise-3.0.3}/pyproject.toml +1 -1
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/__version__.py +1 -1
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/api/__init__.py +43 -4
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/subcommand.py +11 -1
- {greynoise-3.0.2 → greynoise-3.0.3}/tests/cli/test_subcommand.py +30 -3
- {greynoise-3.0.2 → greynoise-3.0.3}/tests/test_api.py +29 -3
- {greynoise-3.0.2 → greynoise-3.0.3}/.circleci/config.yml +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/.dockerignore +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/.editorconfig +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/.github/CODEOWNERS +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/.github/dependabot.yml +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/.github/workflows/slack_release_notice.yml +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/.gitignore +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/.pre-commit-config.yaml +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/.readthedocs.yml +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/.yamllint +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/Dockerfile +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/LICENSE +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/MANIFEST.in +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/Makefile +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/README.rst +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/docs/Makefile +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/docs/source/changelog.rst +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/docs/source/index.rst +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/docs/source/introduction.rst +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/docs/source/reference.rst +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/docs/source/sample.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/docs/source/tutorial.rst +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/pytest.ini +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/release.md +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/requirements/common.txt +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/requirements/dev.txt +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/requirements/docs.txt +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/requirements/test.txt +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/reset_changelog.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/setup.cfg +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/sonar-project.properties +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/__init__.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/api/filter.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/__init__.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/decorator.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/formatter.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/helper.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/parameter.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/analyze.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/cvedetails.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/gnql_query.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/gnql_stats.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/ip_community.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/ip_context.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/ip_context_result.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/ip_multi_context.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/ip_quick_check.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/macros.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/personadetails.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/riot.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/sensoractivity.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/sensorlist.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/similarity.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/stats.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/timeline.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/cli/templates/timelinehourly.txt.j2 +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/exceptions.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/src/greynoise/util.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/tests/cli/test_formatter.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/tests/cli/test_init.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/tests/test_util.py +0 -0
- {greynoise-3.0.2 → greynoise-3.0.3}/tox.ini +0 -0
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version `3.0.3`_
|
|
6
|
+
================
|
|
7
|
+
**Date**: May 15, 2026
|
|
8
|
+
|
|
9
|
+
* API client:
|
|
10
|
+
* Add `exclude_fields` parameter to `query` command
|
|
11
|
+
* Update `stats` to use v3 endpoint
|
|
12
|
+
|
|
5
13
|
Version `3.0.2`_
|
|
6
14
|
================
|
|
7
15
|
**Date**: March 10, 2026
|
|
@@ -483,4 +491,5 @@ Version `0.2.0`_
|
|
|
483
491
|
.. _`2.3.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.2.0...2.3.0
|
|
484
492
|
.. _`3.0.0`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v2.3.0...v3.0.0
|
|
485
493
|
.. _`3.0.1`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v3.0.0...v3.0.1
|
|
486
|
-
.. _`3.0.2`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v3.0.1...
|
|
494
|
+
.. _`3.0.2`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v3.0.1...v3.0.2
|
|
495
|
+
.. _`3.0.3`: https://github.com/GreyNoise-Intelligence/pygreynoise/compare/v3.0.2...HEAD
|
|
@@ -25,7 +25,7 @@ copyright = "2018-2025, GreyNoise Intelligence"
|
|
|
25
25
|
author = "GreyNoise Intelligence"
|
|
26
26
|
|
|
27
27
|
# The full version, including alpha/beta/rc tags
|
|
28
|
-
release = "3.0.
|
|
28
|
+
release = "3.0.3"
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
# -- General configuration ---------------------------------------------------
|
|
@@ -249,7 +249,7 @@ class GreyNoise(BaseAPIClient):
|
|
|
249
249
|
NAME = "GreyNoise"
|
|
250
250
|
EP_GNQL = "v3/gnql"
|
|
251
251
|
EP_GNQL_METADATA = "v3/gnql/metadata"
|
|
252
|
-
EP_GNQL_STATS = "
|
|
252
|
+
EP_GNQL_STATS = "v3/gnql/stats"
|
|
253
253
|
EP_IP = "v3/ip/{ip_address}"
|
|
254
254
|
EP_NOISE_MULTI = "v3/ip?quick=true"
|
|
255
255
|
EP_NOISE_CONTEXT_MULTI = "v3/ip"
|
|
@@ -436,19 +436,58 @@ class GreyNoise(BaseAPIClient):
|
|
|
436
436
|
response = self._request(endpoint)
|
|
437
437
|
return response
|
|
438
438
|
|
|
439
|
-
def query(
|
|
440
|
-
|
|
439
|
+
def query(
|
|
440
|
+
self,
|
|
441
|
+
query,
|
|
442
|
+
size=None,
|
|
443
|
+
scroll=None,
|
|
444
|
+
exclude_raw=False,
|
|
445
|
+
quick=False,
|
|
446
|
+
exclude_fields: Optional[Union[str, List[str]]] = None,
|
|
447
|
+
) -> Dict[str, Any]:
|
|
448
|
+
"""Run GNQL query.
|
|
449
|
+
|
|
450
|
+
:param query: GNQL query
|
|
451
|
+
:type query: str
|
|
452
|
+
:param size: Max number of results to return
|
|
453
|
+
:type size: int
|
|
454
|
+
:param scroll: Scroll token for pagination
|
|
455
|
+
:type scroll: str
|
|
456
|
+
:param exclude_raw: Whether to exclude raw results
|
|
457
|
+
:type exclude_raw: bool
|
|
458
|
+
:param quick: Whether to use quick lookup
|
|
459
|
+
:type quick: bool
|
|
460
|
+
:param exclude_fields: Field names to exclude; comma-separated string or list
|
|
461
|
+
of strings (sent to the API as the ``exclude`` query parameter).
|
|
462
|
+
:type exclude_fields: str or list[str]
|
|
463
|
+
"""
|
|
441
464
|
if self.offering == "community":
|
|
442
465
|
response = {"message": "GNQL not supported with Community offering"}
|
|
443
466
|
else:
|
|
444
467
|
LOGGER.debug(
|
|
445
|
-
"Running GNQL query: %s %s %s %s...",
|
|
468
|
+
"Running GNQL query: %s %s %s %s %s...",
|
|
469
|
+
query,
|
|
470
|
+
size,
|
|
471
|
+
scroll,
|
|
472
|
+
quick,
|
|
473
|
+
exclude_fields,
|
|
446
474
|
)
|
|
447
475
|
params = {"query": query, "quick": quick}
|
|
448
476
|
if size is not None:
|
|
449
477
|
params["size"] = size
|
|
450
478
|
if scroll is not None:
|
|
451
479
|
params["scroll"] = scroll
|
|
480
|
+
if exclude_fields is not None:
|
|
481
|
+
if isinstance(exclude_fields, str):
|
|
482
|
+
normalized_ef = ",".join(
|
|
483
|
+
p.strip() for p in exclude_fields.split(",") if p.strip()
|
|
484
|
+
)
|
|
485
|
+
else:
|
|
486
|
+
normalized_ef = ",".join(
|
|
487
|
+
str(p).strip() for p in exclude_fields if str(p).strip()
|
|
488
|
+
)
|
|
489
|
+
if normalized_ef:
|
|
490
|
+
params["exclude"] = normalized_ef
|
|
452
491
|
if exclude_raw:
|
|
453
492
|
LOGGER.debug("Using GNQL Metadata endpoint")
|
|
454
493
|
endpoint = self.EP_GNQL_METADATA
|
|
@@ -170,6 +170,12 @@ def riot(
|
|
|
170
170
|
|
|
171
171
|
|
|
172
172
|
@gnql_command
|
|
173
|
+
@click.option(
|
|
174
|
+
"--exclude",
|
|
175
|
+
"exclude_fields",
|
|
176
|
+
default=None,
|
|
177
|
+
help="Comma-separated fields to omit from GNQL results (API ``exclude`` param).",
|
|
178
|
+
)
|
|
173
179
|
def query(
|
|
174
180
|
context,
|
|
175
181
|
api_client,
|
|
@@ -182,11 +188,15 @@ def query(
|
|
|
182
188
|
size,
|
|
183
189
|
scroll,
|
|
184
190
|
offering,
|
|
191
|
+
exclude_fields,
|
|
185
192
|
):
|
|
186
193
|
"""Run a GNQL (GreyNoise Query Language) query."""
|
|
187
194
|
queries = get_queries(context, input_file, query)
|
|
188
195
|
results = [
|
|
189
|
-
api_client.query(
|
|
196
|
+
api_client.query(
|
|
197
|
+
query=item, size=size, scroll=scroll, exclude_fields=exclude_fields
|
|
198
|
+
)
|
|
199
|
+
for item in queries
|
|
190
200
|
]
|
|
191
201
|
return results
|
|
192
202
|
|
|
@@ -585,7 +585,9 @@ class TestQuery(object):
|
|
|
585
585
|
result = runner.invoke(subcommand.query, ["-f", "json", query])
|
|
586
586
|
assert result.exit_code == 0
|
|
587
587
|
assert result.output.strip("\n") == expected
|
|
588
|
-
api_client.query.assert_called_with(
|
|
588
|
+
api_client.query.assert_called_with(
|
|
589
|
+
query=query, size=None, scroll=None, exclude_fields=None
|
|
590
|
+
)
|
|
589
591
|
|
|
590
592
|
def test_input_file(self, api_client):
|
|
591
593
|
"""Run query from input file."""
|
|
@@ -598,7 +600,9 @@ class TestQuery(object):
|
|
|
598
600
|
result = runner.invoke(subcommand.query, ["-f", "json", "-i", StringIO(query)])
|
|
599
601
|
assert result.exit_code == 0
|
|
600
602
|
assert result.output.strip("\n") == expected
|
|
601
|
-
api_client.query.assert_called_with(
|
|
603
|
+
api_client.query.assert_called_with(
|
|
604
|
+
query=query, size=None, scroll=None, exclude_fields=None
|
|
605
|
+
)
|
|
602
606
|
|
|
603
607
|
def test_stdin_input(self, api_client):
|
|
604
608
|
"""Run query from stdin."""
|
|
@@ -611,7 +615,30 @@ class TestQuery(object):
|
|
|
611
615
|
result = runner.invoke(subcommand.query, ["-f", "json"], input=query)
|
|
612
616
|
assert result.exit_code == 0
|
|
613
617
|
assert result.output.strip("\n") == expected
|
|
614
|
-
api_client.query.assert_called_with(
|
|
618
|
+
api_client.query.assert_called_with(
|
|
619
|
+
query=query, size=None, scroll=None, exclude_fields=None
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
def test_query_with_exclude(self, api_client):
|
|
623
|
+
"""Run query with --exclude forwarded to the client."""
|
|
624
|
+
runner = CliRunner()
|
|
625
|
+
|
|
626
|
+
query = "<query>"
|
|
627
|
+
api_client.query.return_value = []
|
|
628
|
+
expected = json.dumps([[]], indent=4, sort_keys=True)
|
|
629
|
+
|
|
630
|
+
result = runner.invoke(
|
|
631
|
+
subcommand.query,
|
|
632
|
+
["-f", "json", "--exclude", "raw_data,tls", query],
|
|
633
|
+
)
|
|
634
|
+
assert result.exit_code == 0
|
|
635
|
+
assert result.output.strip("\n") == expected
|
|
636
|
+
api_client.query.assert_called_with(
|
|
637
|
+
query=query,
|
|
638
|
+
size=None,
|
|
639
|
+
scroll=None,
|
|
640
|
+
exclude_fields="raw_data,tls",
|
|
641
|
+
)
|
|
615
642
|
|
|
616
643
|
def test_no_query_passed(self, api_client):
|
|
617
644
|
"""Usage is returned if no query or input file is passed."""
|
|
@@ -727,6 +727,34 @@ class TestQuery(object):
|
|
|
727
727
|
)
|
|
728
728
|
assert response == expected_response
|
|
729
729
|
|
|
730
|
+
def test_query_exclude_fields_as_list(self, client):
|
|
731
|
+
"""exclude_fields is sent as a comma-separated exclude query parameter."""
|
|
732
|
+
query = "<query>"
|
|
733
|
+
client._request = Mock(return_value=[])
|
|
734
|
+
client.query(query, exclude_fields=["raw_data", "tls"])
|
|
735
|
+
client._request.assert_called_with(
|
|
736
|
+
"v3/gnql",
|
|
737
|
+
params={
|
|
738
|
+
"query": query,
|
|
739
|
+
"quick": False,
|
|
740
|
+
"exclude": "raw_data,tls",
|
|
741
|
+
},
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
def test_query_exclude_fields_as_string(self, client):
|
|
745
|
+
"""exclude_fields string is normalized to comma-separated names."""
|
|
746
|
+
query = "<query>"
|
|
747
|
+
client._request = Mock(return_value=[])
|
|
748
|
+
client.query(query, exclude_fields=" raw_data , tls ")
|
|
749
|
+
client._request.assert_called_with(
|
|
750
|
+
"v3/gnql",
|
|
751
|
+
params={
|
|
752
|
+
"query": query,
|
|
753
|
+
"quick": False,
|
|
754
|
+
"exclude": "raw_data,tls",
|
|
755
|
+
},
|
|
756
|
+
)
|
|
757
|
+
|
|
730
758
|
|
|
731
759
|
class TestStats(object):
|
|
732
760
|
"""GreyNoise client run GNQL stats query test cases."""
|
|
@@ -738,9 +766,7 @@ class TestStats(object):
|
|
|
738
766
|
|
|
739
767
|
client._request = Mock(return_value=expected_response)
|
|
740
768
|
response = client.stats(query)
|
|
741
|
-
client._request.assert_called_with(
|
|
742
|
-
"v2/experimental/gnql/stats", params={"query": query}
|
|
743
|
-
)
|
|
769
|
+
client._request.assert_called_with("v3/gnql/stats", params={"query": query})
|
|
744
770
|
assert response == expected_response
|
|
745
771
|
|
|
746
772
|
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|