Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-passivetotal for 
openSUSE:Factory checked in at 2022-03-22 19:39:49
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-passivetotal (Old)
 and      /work/SRC/openSUSE:Factory/.python-passivetotal.new.25692 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-passivetotal"

Tue Mar 22 19:39:49 2022 rev:17 rq:963790 version:2.5.9

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-passivetotal/python-passivetotal.changes  
2022-02-03 23:20:44.266871605 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-passivetotal.new.25692/python-passivetotal.changes
       2022-03-22 19:39:50.991067859 +0100
@@ -1,0 +2,42 @@
+Mon Mar 14 21:39:35 UTC 2022 - Sebastian Wagner <[email protected]>
+
+- - update to version 2.5.9:
+ - Enhancements:
+  - Significant improvements to the Attack Surface Intelligence (ASI) 
documentation. Added
+  - class references for ASI, CTI and vulnerability intelligence to ensure the 
docs and links
+  - generated properly. Introduced a new Sphinx module to help generate inline 
table-of-contents
+  - for complex classes. Corrected typos in docstrings and ensured consistent 
type references
+  - when methods returned RecordList-type objects.
+  - Implemented new config files for readthedocs to align with current 
documentation practices.
+  - New `whois_history` property of `Hostname` and `IPAddress` entities gives 
direct access
+  - to historical Whois (ownership) records. Includes more consistent 
implementation of
+  - RecordList functionality and better pandas dataframe support for both 
historical Whois and 
+  - field-level Whois searches. 
+  - New `impacted_attack_surfaces` property of vulnerability articles 
(`VulnArticle`) filters 
+  - the list of third-party vendors to only those with at least one 
observation. The Illuminate 
+  - API returns all attack surfaces associated with an API key regardless of 
whether they are 
+  - impacted; the complete list is still available in the `attack_surfaces` 
property. Also updated
+  - the `info` view of the Pandas dataframe on a vulnerability article so the 
`impacts` column
+  - shows the count of impacted attack surfaces.
+ - Bug Fixes:
+  - Correctly sum insight and observation counts when accessing Attack Surface 
Insights
+(ASIs) across multiple severity levels. Previously the `active_insight_count`, 
+`total_insight_count`, and `total_observations` properties of the 
`all_active_insights`
+  - record list were only counting high-priority insights. 
+  - Fixed issue that caused an exception when trying to generate a dictionary 
view of an
+  - AttackSurfaceComponent (detection). 
+  - Removed reference to non-existant field in `VulnArticle` that was causing 
an exception when
+  - rendering a vulnerability article as a dictionary with the `as_dict` 
property.
+  - Handle vuln articles with no impacted assets without raising an exception.
+- update to version 2.5.8:
+ - Enhancements:
+  - `certificates` property of `analyzer.Hostname` objects now returns same 
list of SSL
+  - certificates as the UI, enabled by a CertificateField search with the 
field set to
+`name`. This activates special-case functionality in the API that performs a
+  - substring search for a hostname across both subjectAlternativeNames and 
subjectCommonName fields 
+  - The previous version only looked at the `subjectAlternativeNames` field. A 
more narrow
+  - search across specific fields is still available by instantiating an
+`analyzer.CertificateField` object directly.
+  - Docs now show current version number and link to this changelog hosted on 
GitHub.
+
+-------------------------------------------------------------------

Old:
----
  passivetotal-2.5.8.tar.gz

New:
----
  passivetotal-2.5.9.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-passivetotal.spec ++++++
--- /var/tmp/diff_new_pack.pjFfYT/_old  2022-03-22 19:39:51.491068383 +0100
+++ /var/tmp/diff_new_pack.pjFfYT/_new  2022-03-22 19:39:51.495068387 +0100
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %bcond_without test
 Name:           python-passivetotal
-Version:        2.5.8
+Version:        2.5.9
 Release:        0
 Summary:        Client for the PassiveTotal REST API
 License:        GPL-2.0-only

++++++ passivetotal-2.5.8.tar.gz -> passivetotal-2.5.9.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/CHANGELOG.md 
new/passivetotal-2.5.9/CHANGELOG.md
--- old/passivetotal-2.5.8/CHANGELOG.md 2022-01-25 23:36:22.000000000 +0100
+++ new/passivetotal-2.5.9/CHANGELOG.md 2022-03-11 19:30:24.000000000 +0100
@@ -1,5 +1,40 @@
 # Changelog
 
+## v2.5.9
+
+#### Enhancements
+
+- Significant improvements to the Attack Surface Intelligence (ASI) 
documentation. Added
+class references for ASI, CTI and vulnerability intelligence to ensure the 
docs and links
+generated properly. Introduced a new Sphinx module to help generate inline 
table-of-contents
+for complex classes. Corrected typos in docstrings and ensured consistent type 
references
+when methods returned RecordList-type objects.
+- Implemented new config files for readthedocs to align with current 
documentation practices.
+- New `whois_history` property of `Hostname` and `IPAddress` entities gives 
direct access
+to historical Whois (ownership) records. Includes more consistent 
implementation of
+RecordList functionality and better pandas dataframe support for both 
historical Whois and 
+field-level Whois searches. 
+- New `impacted_attack_surfaces` property of vulnerability articles 
(`VulnArticle`) filters 
+the list of third-party vendors to only those with at least one observation. 
The Illuminate 
+API returns all attack surfaces associated with an API key regardless of 
whether they are 
+impacted; the complete list is still available in the `attack_surfaces` 
property. Also updated
+the `info` view of the Pandas dataframe on a vulnerability article so the 
`impacts` column
+shows the count of impacted attack surfaces.
+
+
+#### Bug Fixes
+
+- Correctly sum insight and observation counts when accessing Attack Surface 
Insights
+(ASIs) across multiple severity levels. Previously the `active_insight_count`, 
+`total_insight_count`, and `total_observations` properties of the 
`all_active_insights`
+record list were only counting high-priority insights. 
+- Fixed issue that caused an exception when trying to generate a dictionary 
view of an
+AttackSurfaceComponent (detection). 
+- Removed reference to non-existant field in `VulnArticle` that was causing an 
exception when
+rendering a vulnerability article as a dictionary with the `as_dict` property.
+- Handle vuln articles with no impacted assets without raising an exception.
+
+
 
 ## v2.5.8
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/PKG-INFO 
new/passivetotal-2.5.9/PKG-INFO
--- old/passivetotal-2.5.8/PKG-INFO     2022-01-25 23:37:02.000000000 +0100
+++ new/passivetotal-2.5.9/PKG-INFO     2022-03-11 19:31:28.000000000 +0100
@@ -1,12 +1,12 @@
 Metadata-Version: 2.1
 Name: passivetotal
-Version: 2.5.8
+Version: 2.5.9
 Summary: Library for the RiskIQ PassiveTotal and Illuminate API
 Home-page: https://github.com/passivetotal/python_api
+Download-URL: https://github.com/passivetotal/python_api/archive/master.zip
 Author: RiskIQ
 Author-email: [email protected]
 License: GPLv2
-Download-URL: https://github.com/passivetotal/python_api/archive/master.zip
 Keywords: threats,research,analysis
 Platform: UNKNOWN
 Description-Content-Type: text/markdown
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/docs/analyzer.rst 
new/passivetotal-2.5.9/docs/analyzer.rst
--- old/passivetotal-2.5.8/docs/analyzer.rst    2021-09-27 17:47:56.000000000 
+0200
+++ new/passivetotal-2.5.9/docs/analyzer.rst    2022-03-11 19:30:24.000000000 
+0100
@@ -72,8 +72,8 @@
    :inherited-members:
 
 
-Module Reference
-----------------
+Analyzer Module Reference
+-------------------------
 .. automodule:: passivetotal.analyzer
     :members:
    
@@ -98,6 +98,8 @@
 
 Whois Records
 -------------
+Domain Name Whois
+^^^^^^^^^^^^^^^^^
 The `whois` property for host names returns the DomainWhois record for
 the registered domain name portion of the host name. 
 
@@ -113,7 +115,50 @@
 prepared for missing or malformed data. Access the `raw` record for the API 
response 
 directly as a Python dict or use the `record` property to get the raw Whois 
response.
 
-The RiskIQ PassiveTotal API can  search Whois records by field to find related
+IP Whois Records
+^^^^^^^^^^^^^^^^
+The `whois` property is also available for IP addresses. Some of the fields 
are different
+than domain whois records, but most are the same.
+
+.. code-block:: python
+
+   >>> from passivetotal import analyzer
+   >>> analyzer.init()
+   >>> print(analyzer.IPAddress('160.69.1.37').whois.organization)
+   PACCAR, Inc.
+
+
+Using Whois Fields
+^^^^^^^^^^^^^^^^^^
+Fields in the IP and Hostname Whois record are returned as `WhoisField` 
objects to faciliate
+field-level searching. Cast the field as a string if you need to get the 
actual value:
+
+.. code-block:: python
+ 
+   >>> org = analyzer.IPAddress('160.69.1.37').whois.organization
+   >>> org
+   WhoisField('organization','PACCAR, Inc.')
+   >>> print(org)
+   Paccar, Inc.
+   >>> org_str = str(org)
+   >>> org_str
+   Paccar, Inc.
+
+You can also start with a field name and a string and search for it directly 
to find domains
+or IPs associated with a name, email address, or other supported field type. 
+
+.. code-block:: python
+
+   >>> from passivetotal.analyzer.whois import WhoisField
+   >>> WhoisField('email','[email protected]').records.domains
+   
+
+
+
+
+Search Whois Records
+^^^^^^^^^^^^^^^^^^^^
+The RiskIQ PassiveTotal API can search Whois records by field to find related
 domain names with the same contact information. Use the `records` property of
 supported fields (any property that returns type `WhoisField`).
 
@@ -123,11 +168,65 @@
    >>> analyzer.Hostname('riskiq.net').whois.organization.records.domains
    {Hostname('riskiq.com'), Hostname('riskiq.net'), Hostname('riskiqeg.com')}
 
+The `records` property returns the same type of list-like object that other 
analyzer
+objects return, so you can filter, sort, and convert to pandas DataFrames as 
needed.
+See below for reference.
+
+
+Historical Whois Records
+^^^^^^^^^^^^^^^^^^^^^^^^
+Historical whois records may be available for domain names and IP addresses. 
Access the
+`whois_history` property of an `IPAddress` or `Hostname` object to obtain a 
list of historically
+observed records as a standard analyzer `RecordList`.
+
+
+.. code-block:: python
+
+    >>> from passivetotal import analyzer
+    >>> for record in analyzer.Hostname('passivetotal.org').whois_history:
+            print(record.last_seen, record.registrant_email)
+    2022-02-11 19:16:29.334000-08:00 
[email protected]
+    2021-04-05 09:13:10.330000-07:00 
[email protected]
+    2021-04-04 08:40:41.295000-07:00 
[email protected]
+    2021-04-03 16:25:42.455000-07:00 
[email protected]
+    2021-04-06 08:42:07.273000-07:00 
[email protected]
+    2021-03-04 15:45:41.026000-08:00 
[email protected]
+    2021-03-03 13:56:05.605000-08:00 
[email protected]
+    2020-10-06 02:41:38.359000-07:00 
[email protected]
+    2020-10-06 14:01:33.575000-07:00 
[email protected]
+    2020-08-21 04:20:43.757000-07:00 
[email protected]
+    2020-06-20 03:57:01.411000-07:00 [email protected]
+    2020-03-16 15:22:46.043000-07:00 [email protected]
+    2020-03-17 06:57:02.899000-07:00 [email protected]
+    2019-09-29 07:45:02.096000-07:00 [email protected]
+
+In this example, we accessed only two fields of each record, but the complete 
Whois record
+remains available. Consider using the `as_dict` or `as_df` properties of the 
`whois_history`
+object to get the complete list as a Python dictionary or a Pandas dataframe.
+
+.. code-block:: python
+
+    >>> analyzer.Hostname('passivetotal.org').whois_history.as_dict
+    {'records': [{'admin': {'email': 
'[email protected]'},
+    'billing': {},
+    'registrant': {'country': 'US',
+        'email': '[email protected]',
+    ...
+
+
+
+Whois Object Reference
+^^^^^^^^^^^^^^^^^^^^^^
+
 
 .. autoclass:: passivetotal.analyzer.whois.DomainWhois
    :members:
    :inherited-members:
 
+.. autoclass:: passivetotal.analyzer.whois.IPWhois
+   :members:
+   :inherited-members:
+
 .. autoclass:: passivetotal.analyzer.whois.WhoisField
    :members:
    :inherited-members:
@@ -136,6 +235,18 @@
    :members:
    :inherited-members:
 
+.. autoclass:: passivetotal.analyzer.whois.HistoricalWhoisRecords
+    :members:
+    :inherited-members:
+
+.. autoclass:: passivetotal.analyzer.whois.HistoricalDomainWhois
+    :members:
+    :inherited-members:
+
+.. autoclass:: passivetotal.analyzer.whois.HistoricalIPWhois
+    :members:
+    :inherited-members:
+
 
 
 Threat Intel Articles
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/docs/conf.py 
new/passivetotal-2.5.9/docs/conf.py
--- old/passivetotal-2.5.8/docs/conf.py 2021-07-15 21:35:42.000000000 +0200
+++ new/passivetotal-2.5.9/docs/conf.py 2022-03-11 19:30:24.000000000 +0100
@@ -31,10 +31,17 @@
 # ones.
 extensions = [
     'sphinx.ext.autodoc',
+    'sphinx.ext.autosummary',
     'sphinx.ext.intersphinx',
     'sphinx.ext.todo',
     'sphinx.ext.coverage',
     'sphinx.ext.ifconfig',
+    'autoclasstoc',
+]
+
+autoclasstoc_sections = [
+    'public-attrs',
+    'public-methods',
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -294,7 +301,9 @@
 import os
 on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
 
-if not on_rtd:  # only import and set the theme if we're building docs locally
+if on_rtd:
+    html_theme = 'default'
+else:
     import sphinx_rtd_theme
     html_theme = 'sphinx_rtd_theme'
     html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/docs/illuminate.rst 
new/passivetotal-2.5.9/docs/illuminate.rst
--- old/passivetotal-2.5.8/docs/illuminate.rst  2021-09-27 17:47:56.000000000 
+0200
+++ new/passivetotal-2.5.9/docs/illuminate.rst  2022-03-11 19:30:24.000000000 
+0100
@@ -189,7 +189,7 @@
 
     >>> insights = my_asi.medium_priority_insights.only_active_insights
     >>> first_insight = insights[0]
-    >>> for observation in first_insight:
+    >>> for observation in first_insight.observations:
             print(observation.type)
             print(observation.name)
     HOST
@@ -289,32 +289,53 @@
 ASI Reference
 ^^^^^^^^^^^^^
 
+.. autosummary::
+    :nosignatures:
+
+    passivetotal.analyzer.illuminate.asi.AttackSurfaces
+    passivetotal.analyzer.illuminate.asi.AttackSurface
+    passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights
+    passivetotal.analyzer.illuminate.asi.AttackSurfaceInsight
+    passivetotal.analyzer.illuminate.asi.AttackSurfaceObservations
+    passivetotal.analyzer.illuminate.asi.AttackSurfaceObservation
+
 .. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurfaces
     :members:
     :inherited-members:
 
+    .. autoclasstoc::
+
 .. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurface
     :members:
     :inherited-members:
 
+    .. autoclasstoc::
+
 .. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights
     :members:
     :inherited-members:
 
     .. automethod:: __init__
+    .. autoclasstoc::
 
 .. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurfaceInsight
     :members:
     :inherited-members:
 
+    .. autoclasstoc::
+
 .. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurfaceObservations
     :members:
     :inherited-members:
 
+    .. autoclasstoc::
+
 .. autoclass:: passivetotal.analyzer.illuminate.asi.AttackSurfaceObservation
     :members:
     :inherited-members:
 
+    .. autoclasstoc::
+
 
 
 
@@ -477,24 +498,40 @@
 CTI Reference
 ^^^^^^^^^^^^^
 
+.. autosummary::
+    :nosignatures:
+
+    passivetotal.analyzer.illuminate.cti.IntelProfiles
+    passivetotal.analyzer.illuminate.cti.IntelProfile
+    passivetotal.analyzer.illuminate.cti.IntelProfileIndicatorList
+    passivetotal.analyzer.illuminate.cti.IntelProfileIndicator
+
+
 .. autoclass:: passivetotal.analyzer.illuminate.cti.IntelProfiles
     :members:
     :inherited-members:
 
+    .. autoclasstoc::
+
 .. autoclass:: passivetotal.analyzer.illuminate.cti.IntelProfile
     :members:
     :inherited-members: tuple
 
+    .. autoclasstoc::
+
 .. autoclass:: passivetotal.analyzer.illuminate.cti.IntelProfileIndicatorList
     :members:
     :inherited-members:
 
     .. automethod:: __init__
+    .. autoclasstoc::
 
 .. autoclass:: passivetotal.analyzer.illuminate.cti.IntelProfileIndicator
     :members:
     :inherited-members:
 
+    .. autoclasstoc::
+
 
 
 Vulnerability Intelligence
@@ -614,30 +651,69 @@
 Vuln Reference
 ^^^^^^^^^^^^^^
 
+.. autosummary::
+    :nosignatures:
+
+    passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEs
+    passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVE
+    passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEObservations
+    passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEObservation
+    passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponents
+    passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponent
+    passivetotal.analyzer.illuminate.vuln.VulnArticle
+    passivetotal.analyzer.illuminate.vuln.VulnArticleImpacts
+    passivetotal.analyzer.illuminate.vuln.VulnArticleImpact
+
 .. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEs
     :members:
     :inherited-members:
 
+    .. autoclasstoc::
+
 .. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVE
     :members:
     :inherited-members:
 
+    .. autoclasstoc::
+
 .. autoclass:: 
passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEObservations
     :members:
     :inherited-members:
 
+    .. autoclasstoc::
+
 .. autoclass:: 
passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEObservation
     :members:
     :inherited-members:
 
-.. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEComponents
+    .. autoclasstoc::
+
+.. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponents
     :members:
     :inherited-members:
 
-.. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEComponent
+    .. autoclasstoc::
+
+.. autoclass:: passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponent
     :members:
     :inherited-members:
 
+    .. autoclasstoc::
+
 .. autoclass:: passivetotal.analyzer.illuminate.vuln.VulnArticle
     :members:
-    :inherited-members:
\ No newline at end of file
+    :inherited-members:
+
+    .. autoclasstoc::
+
+.. autoclass:: passivetotal.analyzer.illuminate.vuln.VulnArticleImpacts
+    :members:
+    :inherited-members:
+
+    .. autoclasstoc::
+
+.. autoclass:: passivetotal.analyzer.illuminate.vuln.VulnArticleImpact
+    :members:
+    :inherited-members:
+
+    .. autoclasstoc::
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/docs/requirements.txt 
new/passivetotal-2.5.9/docs/requirements.txt
--- old/passivetotal-2.5.8/docs/requirements.txt        1970-01-01 
01:00:00.000000000 +0100
+++ new/passivetotal-2.5.9/docs/requirements.txt        2022-03-11 
19:30:24.000000000 +0100
@@ -0,0 +1 @@
+autoclasstoc==1.3.0
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/_version.py 
new/passivetotal-2.5.9/passivetotal/_version.py
--- old/passivetotal-2.5.8/passivetotal/_version.py     2022-01-25 
23:36:22.000000000 +0100
+++ new/passivetotal-2.5.9/passivetotal/_version.py     2022-03-11 
19:30:24.000000000 +0100
@@ -1 +1 @@
-VERSION="2.5.8"
\ No newline at end of file
+VERSION="2.5.9"
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/analyzer/_common.py 
new/passivetotal-2.5.9/passivetotal/analyzer/_common.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/_common.py     2021-10-22 
01:31:48.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/_common.py     2022-03-11 
19:30:24.000000000 +0100
@@ -96,7 +96,7 @@
     def as_df(self):
         """Get this object as a Pandas DataFrame.
 
-        Use `to_dataframe()' instead if you need to control how the dataframe 
is built.
+        Use `to_dataframe()` instead if you need to control how the dataframe 
is built.
 
         Requires the pandas Python library. Throws `AnalyzerError` if it is 
missing.
         :rtype: :class:`pandas.DataFrame`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/analyzer/hostname.py 
new/passivetotal-2.5.9/passivetotal/analyzer/hostname.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/hostname.py    2022-01-25 
23:36:22.000000000 +0100
+++ new/passivetotal-2.5.9/passivetotal/analyzer/hostname.py    2022-03-11 
19:30:24.000000000 +0100
@@ -6,7 +6,7 @@
 from passivetotal.analyzer._common import is_ip, refang, AnalyzerError
 from passivetotal.analyzer.pdns import HasResolutions
 from passivetotal.analyzer.summary import HostnameSummary, HasSummary
-from passivetotal.analyzer.whois import DomainWhois
+from passivetotal.analyzer.whois import DomainWhois, HistoricalWhoisRecords
 from passivetotal.analyzer.ssl import CertificateField
 from passivetotal.analyzer.hostpairs import HasHostpairs
 from passivetotal.analyzer.cookies import HasCookies
@@ -96,6 +96,13 @@
         self._whois = DomainWhois(response)
         return self._whois
     
+    def _api_get_whois_history(self):
+        """Query the Whois API for historical records on this hostname."""
+        response = get_api('Whois').get_whois_details(query=self._hostname, 
compact_record=False, history=True)
+        self._whois_history = HistoricalWhoisRecords()
+        self._whois_history.parse(response)
+        return self._whois_history
+    
     def _query_dns(self):
         """Perform a DNS lookup."""
         try:
@@ -195,6 +202,16 @@
         )
     
     @property
+    def whois_history(self):
+        """Historical Whois records for this hostname.
+        
+        :rtype: :class:`passivetotal.analyzer.whois.HistoricalWhoisRecords`
+        """
+        if getattr(self, '_whois_history', None) is not None:
+            return self._whois_history
+        return self._api_get_whois_history()
+    
+    @property
     def is_ip(self):
         """Whether this object is an IP. Always returns false. 
         
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/asi.py 
new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/asi.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/asi.py      
2021-10-22 01:22:41.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/asi.py      
2022-03-11 19:30:24.000000000 +0100
@@ -17,7 +17,8 @@
 
 class AttackSurfaces(RecordList, PagedRecordList, ForPandas):
 
-    """List of RiskIQ Illuminate Attack Surfaces.
+    """Collection of RiskIQ Illuminate Attack Surfaces in a list-like object
+    containing :class:`AttackSurface` objects.
     
     Primarily used for enumerating a set of third-party vendors.
     """
@@ -157,7 +158,7 @@
         """Get insights at a level (high, medium or low).
         
         :param level: Priority level (high, medium, or low).
-        :returns: List of :class:`AttackSurfaceInsights`
+        :returns: :class:`AttackSurfaceInsights`
         """
         self._ensure_valid_level(level)
         if self._insights[level] is not None:
@@ -178,6 +179,7 @@
         """Get a list of CVEs impacting assets in this attack surface.
         
         :param pagesize: Size of pages to retrieve from the API.
+        :rtype: 
:class:`passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEs`
         """
         from passivetotal.analyzer.illuminate import AttackSurfaceCVEs
         self._cves = AttackSurfaceCVEs(self, pagesize)
@@ -188,6 +190,8 @@
         """Get a list of vulnerable components (detections) in this attack 
surface.
         
         :param pagesize: Size of pages to retrieve from the API.
+
+        :rtype: 
:class:`passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponents`
         """
         from passivetotal.analyzer.illuminate import AttackSurfaceComponents
         self._components = AttackSurfaceComponents(self, pagesize)
@@ -214,12 +218,17 @@
         
         :rtype: 
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
         """
-        insights = self.high_priority_insights._make_shallow_copy()
-        insights._level = 'ALL'
-        insights._records = []
-        for level in self._LEVELS:
-            insights._records.extend(self.get_insights(level)._records)
-        return insights
+        insights_all = self.high_priority_insights._make_shallow_copy()
+        count_fields = 
['_count_active_insights','_count_total_insights','_count_total_observations']
+        for field in count_fields:
+            setattr(insights_all, field, 0)
+        insights_all._level = 'ALL'
+        insights_all._records = []
+        for level in [self.get_insights(level) for level in self._LEVELS]:
+            insights_all._records.extend(level._records)
+            for field in count_fields:
+                setattr(insights_all, field, getattr(insights_all, field) + 
getattr(level, field))
+        return insights_all
     
     @property
     def all_active_insights(self):
@@ -266,7 +275,7 @@
     def high_priority_insights(self):
         """Get high priority insights.
         
-        :rtype: List of 
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
+        :rtype: 
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
         """
         return self.get_insights('high')
     
@@ -274,7 +283,7 @@
     def medium_priority_insights(self):
         """Get medium priority insights.
         
-        :rtype: List of 
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
+        :rtype: 
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
         """
         return self.get_insights('medium')
     
@@ -282,7 +291,7 @@
     def low_priority_insights(self):
         """Get low priority insights.
         
-        :rtype: List of 
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
+        :rtype: 
:class:`passivetotal.analyzer.illuminate.asi.AttackSurfaceInsights`
         """
         return self.get_insights('low')
     
@@ -290,7 +299,7 @@
     def cves(self):
         """Get a list of CVEs associated with this attack surface.
         
-        :rtype: List of 
:class:`passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVE`
+        :rtype: 
:class:`passivetotal.analyzer.illuminate.vuln.AttackSurfaceCVEs`
         """
         if getattr(self, '_cves', None) is not None:
             return self._cves
@@ -298,7 +307,10 @@
 
     @property
     def components(self):
-        """List of components (detections) vulnerable to this CVE in this 
attack surface."""
+        """List of components (detections) vulnerable to this CVE in this 
attack surface.
+        
+        :rtype: 
:class:`passivetotal.analyzer.illuminate.vuln.AttackSurfaceComponents`
+        """
         if getattr(self, '_components', None) is not None:
             return self._components
         return self.get_components()
@@ -306,7 +318,8 @@
 
 class AttackSurfaceInsights(RecordList, ForPandas):
 
-    """List of insights associated with an attack surface."""
+    """Collection of insights associated with an attack surface in a list-like 
object
+    containing :class:`AttackSurfaceInsight` objects."""
 
     def __init__(self, attack_surface=None, level=None, api_response=None):
         self._attack_surface = attack_surface
@@ -345,7 +358,7 @@
     
     @property
     def attack_surface(self):
-        """Attach surface these insights are associated with.
+        """Attack surface these insights are associated with.
         
         :rtype: :class:`passivetotal.analyzer.illuminate.asi.AttackSurface`
         """
@@ -363,7 +376,7 @@
     def only_active_insights(self):
         """Filter to only active insights (insights with active observations).
         
-        :rtype: bool
+        :rtype: :class:`AttackSurfaceInsights`
         """
         return self.filter(has_observations=True)
     
@@ -466,6 +479,7 @@
         """Get a list of impacted assets (observations).
         
         :param pagesize: Size of pages to retrieve from the API.
+        :rtype: :class:`AttackSurfaceObservations`
         """
         self._observations = AttackSurfaceObservations(self, self._group_by, 
self._segment_by)
         self._observations.load_all_pages()
@@ -473,7 +487,10 @@
 
     @property
     def observations(self):
-        """List of impacted assets."""
+        """List of impacted assets.
+        
+        :rtype: :class:`AttackSurfaceObservations`
+        """
         if getattr(self, '_observations', None) is not None:
             return self._observations
         return self.get_observations()
@@ -482,7 +499,8 @@
 
 class AttackSurfaceObservations(RecordList, PagedRecordList, ForPandas):
 
-    """List of observations (assets) associated with an attack surface 
insight."""
+    """Collection of observations (assets) associated with an attack surface 
insight
+    in a list-like object containing :class:`AttackSurfaceObservation` 
objects."""
 
     def __init__(self, insight, group_by, segment_by, pagesize=400):
         self._totalrecords = None
@@ -548,6 +566,8 @@
 
 
 class AttackSurfaceObservation(Record, FirstLastSeen, ForPandas):
+    """An attack surface asset (typically identified by an IP address or 
hostname) that
+    is impacted by an observation."""
     
     def __init__(self, insight, api_response):
         self._insight = insight
@@ -579,18 +599,29 @@
 
     @property
     def type(self):
+        """Type of impacted asset (i.e. HOST or IP_ADDRESS)."""
         return self._type
     
     @property
     def name(self):
+        """Name of impacted asset (i.e. the IP address or hostname)."""
         return self._name
     
     @property
     def insight(self):
+        """Attack surface insight associated with this observation.
+        
+        :rtype: :class:`AttackSurfaceInsight`
+        """
         return self._insight
     
     @property
     def hostname(self):
+        """Shortcut property to access an `analyzer.Hostname` object from this
+        observation, if the observation type is HOST, else returns `None`.
+        
+        :rtype: :class:`passivetotal.analyzer.Hostname`
+        """
         if self._type != 'HOST':
             return None
         try:
@@ -600,6 +631,11 @@
     
     @property
     def ip(self):
+        """Shortcut property to access an `analyzer.IPAddress` object from this
+        observation, if the observation type is IP_ADDRESS, else returns 
`None`.
+        
+        :rtype: :class:`passivetotal.analyzer.IPAddress`
+        """
         if self._type != 'IP_ADDRESS':
             return None
         try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/cti.py 
new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/cti.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/cti.py      
2021-09-27 17:47:56.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/cti.py      
2022-03-11 19:30:24.000000000 +0100
@@ -212,7 +212,7 @@
     def indicators(self):
         """Unfiltered indicator list associated with this intel profile.
 
-        Calls `passivetotal.analyzer.illuminate.IntelProfile.get_indicators()'
+        Calls `passivetotal.analyzer.illuminate.IntelProfile.get_indicators()`
         with default parameters. Use that method directly for more granular 
control.
 
         :rtype: 
:class:`passivetotal.analyzer.illuminate.cti.IntelProfileIndicatorList`
@@ -245,7 +245,8 @@
 class IntelProfileIndicatorList(RecordList, PagedRecordList, ForPandas):
 
     def __init__(self, profile_id=None, query=None, types=None, 
categories=None, sources=None, pagesize=INDICATOR_PAGE_SIZE):
-        """List of indicators associated with a RiskIQ Intel Profile.
+        """Collection of indicators associated with a RiskIQ Intel Profile as 
a list-like object
+        containing :class:`IntelProfileIndicator` objects.
         
         :param profile_id: Threat intel profile ID to search for.
         :param query: Indicator value to query for.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/vuln.py 
new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/vuln.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/illuminate/vuln.py     
2021-10-22 01:31:48.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/illuminate/vuln.py     
2022-03-11 19:30:24.000000000 +0100
@@ -15,7 +15,8 @@
     
 class AttackSurfaceCVEs(RecordList, PagedRecordList, ForPandas):
 
-    """List of CVEs associated with an attack surface."""
+    """Collection of CVEs associated with an attack surface as a list-like 
object
+    containing :class:`AttackSurfaceCVE` objects."""
 
     def __init__(self, attack_surface=None, pagesize=400):
         self._totalrecords = None
@@ -107,6 +108,7 @@
         """Get a list of observations(assets) vulnerable to this CVE in this 
attack surface.
         
         :param pagesize: Size of pages to retrieve from the API.
+        :rtype: :class:`AttackSurfaceCVEObservations`
         """
         self._observations = AttackSurfaceCVEObservations(self, pagesize)
         self._observations.load_all_pages()
@@ -185,7 +187,8 @@
 
 class AttackSurfaceCVEObservations(RecordList, PagedRecordList, ForPandas):
 
-    """List of observations (assets) associated with a CVE in a specific 
attack surface."""
+    """Collection of observations (assets) associated with a CVE in a specific 
attack surface
+    as a list-like object containing :class:`AttackSurfaceCVEObservation` 
objects."""
 
     def __init__(self, cve=None, pagesize=400):
         self._totalrecords = None
@@ -209,8 +212,11 @@
         self._totalrecords = api_response.get('totalCount')
         if self._pagination_current_page == 0:
             self._records = []
-        for result in api_response.get('assets',[]):
-            self._records.append(AttackSurfaceCVEObservation(self._cve, 
result))
+        try:
+            for result in api_response.get('assets',[]):
+                self._records.append(AttackSurfaceCVEObservation(self._cve, 
result))
+        except TypeError:
+            pass # assets may be blank instead of an empty list 
     
     @property
     def cve(self):
@@ -309,7 +315,8 @@
 
 class AttackSurfaceComponents(RecordList, PagedRecordList, ForPandas):
 
-    """List of vulnerable components (detections) associated with an attack 
surface."""
+    """Collection of vulnerable components (detections) associated with an 
attack surface
+    as a list-like object containing :class:`AttackSurfaceComponent` 
objects."""
 
     def __init__(self, attack_surface=None, pagesize=400):
         self._totalrecords = None
@@ -338,7 +345,7 @@
     
     @property
     def attack_surface(self):
-        """Get the CVE associated with this list of observations.
+        """Get the attack surface associated with this list of observations.
         
         :rtype: :class:`passivetotal.analyzer.illuminate.asi.AttackSurface`
         """
@@ -377,7 +384,7 @@
         return '<AttackSurfaceComponent {0.type}:{0.name}>'.format(self)
     
     def _get_dict_fields(self):
-        return ['vendor_id','type','name','severity','count']
+        return ['type','name','severity','count']
     
     def to_dataframe(self):
         """Render this object as a Pandas dataframe.
@@ -477,7 +484,7 @@
     
     def _get_dict_fields(self):
         return 
['id','description','cwes','score','cvss2score','cvss3score','str:date_published',
-                
'str:date_updated','str:date_publisher_updated','references','components',
+                'str:date_publisher_updated','references','components',
                 'observation_count']
     
     def to_dataframe(self, view='info'):
@@ -511,7 +518,7 @@
                 'observations': self.observation_count,
                 'references': len(self.references),
                 'components': len(self.components),
-                'impacts': len(self._impacted3p)
+                'impacts': len(self.impacted_attack_surfaces)
             }],
             'references': [{
                     'cve_id': self.id,
@@ -636,13 +643,26 @@
         return VulnArticleImpacts(self, self._impacted3p)
     
     @property
+    @lru_cache(maxsize=None)
+    def impacted_attack_surfaces(self):
+        """List of all attack surfaces associated with your API account that 
are impacted
+        by this vulnerability (they have at least one observation of an 
impacted asset).
+        
+        :rtype: 
:class:`passivetotal.analyzer.illuminate.vuln.VulnArticleImpacts`
+        """
+        return self.attack_surfaces.only_impacted
+    
+    @property
     def observation_count(self):
         """Number of observations (assets) within the primary attack surface 
that are impacted by this vulnerability."""
         return self._observation_count
     
     @property
     def observations(self):
-        """List of observations (assets) within the primary attack surface 
that are impacted by this vulnerability."""
+        """List of observations (assets) within the primary attack surface 
that are impacted by this vulnerability.
+        
+        :rtype: :class:`AttackSurfaceCVEObservations`
+        """
         from . import AttackSurface
         attack_surface = AttackSurface.load()
         article = {
@@ -659,7 +679,8 @@
 
 class VulnArticleImpacts(RecordList, ForPandas):
 
-    """List of Illuminate Attack Surfaces impacted by a vulnerability."""
+    """Collection of Illuminate Attack Surfaces potentially impacted by a 
vulnerability 
+    as a list-like object containing :class:`VulnArticleImpact` objects."""
 
     def __init__(self, article=None, impacts=[]):
         self._records = []
@@ -672,7 +693,7 @@
         return '<VulnArticleImpacts {0.article.id}>'.format(self)
     
     def __str__(self):
-        return '{0.article.id} impacts {0.impact_count:,} attack 
surfaces(s)'.format(self)
+        return '{0.article.id} impacts {0.impact_count:,} of {0.length:,} 
attack surfaces(s)'.format(self)
 
     def _get_dict_fields(self):
         return ['cve_id', 'impact_count']
@@ -685,14 +706,24 @@
     
     @property
     def article(self):
-        """Article that describes the vulnerability impacting these attack 
surfaces."""
+        """Article that describes the vulnerability impacting these attack 
surfaces.
+        
+        :rtype: :class:`VulnArticle`
+        """
         return self._article
     
     @property
     def attack_surfaces(self):
-        """List of impacted attack surfaces.
+        """Unfiltered list of all attack surfaces associated with your API 
account.
+
+        This is the same list you would get if you iterated through an 
instance of this
+        object. Each record will include a count of assets impacted by a 
vulnerability, in the
+        "observation_count" property. The count may be zero.
+
+        If you need a list of only impacted attack surfaces, use the 
`only_impacted`
+        property instead.
         
-        :rtypte: 
:class:`passivetotal.analyzer.illuminate.vuln.VulnArticleImpact`
+        :rtype: 
:class:`passivetotal.analyzer.illuminate.vuln.VulnArticleImpacts`
         """
         return self._records
     
@@ -702,9 +733,20 @@
         return self.article.id
     
     @property
+    def only_impacted(self):
+        """Filter the list to include only articles with at least one 
observation (asset).
+        
+        :rtype: :class:`VulnArticleImpacts`
+        """
+        return self.filter_fn(lambda asi: asi.observation_count > 0)
+    
+    @property
     def impact_count(self):
-        """Number of attack surfaces impacted by this vulnerability."""
-        return len(self._records)
+        """Number of attack surfaces with assets impacted by this 
vulnerability.
+        
+        Counts the number of records that have more than zero observations.
+        """
+        return len(self.only_impacted)
         
     
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/analyzer/ip.py 
new/passivetotal-2.5.9/passivetotal/analyzer/ip.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/ip.py  2021-08-19 
02:49:22.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/ip.py  2022-03-11 
19:30:24.000000000 +0100
@@ -3,7 +3,7 @@
 
 from passivetotal.analyzer import get_api, get_config
 from passivetotal.analyzer._common import is_ip, refang, AnalyzerError
-from passivetotal.analyzer.whois import IPWhois
+from passivetotal.analyzer.whois import IPWhois, HistoricalWhoisRecords
 from passivetotal.analyzer.pdns import HasResolutions
 from passivetotal.analyzer.services import Services
 from passivetotal.analyzer.ssl import Certificates
@@ -103,11 +103,18 @@
         return self._ssl_history
 
     def _api_get_whois(self):
-        """Query the pDNS API for resolution history."""
+        """Query the whois API."""
         response = get_api('Whois').get_whois_details(query=self._ip)
         self._whois = IPWhois(response)
         return self._whois
     
+    def _api_get_whois_history(self):
+        """Query the whois API for ownership history."""
+        response = get_api('Whois').get_whois_details(query=self._ip, 
history=True)
+        self._whois_history = HistoricalWhoisRecords()
+        self._whois_history.parse(response)
+        return self._whois_history
+    
     @property
     def ip(self):
         """IP address as a string."""
@@ -145,6 +152,16 @@
         return self._api_get_whois()
     
     @property
+    def whois_history(self):
+        """Historical Whois (ownership) records for this IP.
+
+        :rtype: :class:`passivetotal.analyzer.whois.HistoricalWhoisRecords`
+        """
+        if getattr(self, '_whois_history', None) is not None:
+            return self._whois_history
+        return self._api_get_whois_history()
+    
+    @property
     def is_ip(self):
         """Whether this object is an IP. Always returns true. 
         
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/analyzer/ssl.py 
new/passivetotal-2.5.9/passivetotal/analyzer/ssl.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/ssl.py 2021-06-29 
20:04:38.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/ssl.py 2022-03-11 
19:30:24.000000000 +0100
@@ -44,7 +44,7 @@
     
     @property
     def not_expired(self):
-        """Filtered list of :class:`Certificates' that have not expired."""
+        """Filtered list of :class:`Certificates` that have not expired."""
         return self.filter(expired=False)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal/analyzer/whois.py 
new/passivetotal-2.5.9/passivetotal/analyzer/whois.py
--- old/passivetotal-2.5.8/passivetotal/analyzer/whois.py       2021-06-21 
16:37:13.000000000 +0200
+++ new/passivetotal-2.5.9/passivetotal/analyzer/whois.py       2022-03-11 
19:30:24.000000000 +0100
@@ -2,7 +2,7 @@
 from collections import namedtuple, OrderedDict
 import pprint
 from passivetotal.analyzer import get_api, get_object
-from passivetotal.analyzer._common import RecordList, ForPandas
+from passivetotal.analyzer._common import is_ip, Record, RecordList, ForPandas
 
 
 
@@ -44,6 +44,14 @@
 
     def __repr__(self):
         return "WhoisField('{0.name}','{0.value}')".format(self)
+    
+    def __eq__(self, other):
+        if isinstance(other, str):
+            return self._value == other
+        return other is self
+    
+    def __hash__(self):
+        return self._value.__hash__()
 
     def _api_search(self):
         """Use the 'Whois' request wrapper to perform a keyword search by 
field."""
@@ -70,7 +78,7 @@
 
 
 
-class WhoisRecords(RecordList):
+class WhoisRecords(RecordList, ForPandas):
 
     """List of Whois records."""
 
@@ -78,11 +86,13 @@
         return []
     
     def _get_sortable_fields(self):
-        return ['domain']
+        return ['host']
     
     def parse(self, api_response):
         """Parse an API response into a list of `DomainWhois` records."""
-        self._records = list(map(DomainWhois, api_response.get('results',[])))
+        self._records = [
+            IPWhois(r) if is_ip(r['domain']) else DomainWhois(r) for r in 
api_response.get('results',[])
+        ]
     
     @property
     def domains(self):
@@ -103,10 +113,25 @@
     def orgs(self):
         """Return a set of unique org names in this record list."""
         return set([r.organization for r in self if r.organization])
-    
 
 
-class WhoisRecord(ForPandas):
+
+class HistoricalWhoisRecords(WhoisRecords):
+
+    """List of :class:`HistoricalDomainWhois` or :class:`HistoricalIPWhois` 
records."""
+
+    def _get_sortable_fields(self):
+        return ['str:host','last_seen']
+
+    def parse(self, api_response):
+        """Parse an API response into a list of `HistoricalDomainWhois` or 
`HistoricalIPWhois` records."""
+        self._records = [
+            HistoricalIPWhois(r) if is_ip(r['domain']) else 
HistoricalDomainWhois(r) for r in api_response.get('results',[])
+        ]
+       
+
+
+class WhoisRecord(Record, ForPandas):
     """Base type for IP and Domain Whois."""
 
     def _get_contacts(self, contact_type):
@@ -339,6 +364,12 @@
         """Raw API response."""
         return self._rawrecord
 
+    @property
+    def host(self):
+        """Get the IP or domain name this record is associated with, as an
+        :class:`analyzer.IPAddress` or :class:`analyzer.Hostname` object."""
+        return get_object(self._domain, type = 'IPAddress' if self.is_ip else 
'Hostname')
+
 
 
 class DomainWhois(WhoisRecord):
@@ -386,6 +417,46 @@
         :rtype: datetime
         """
         return self._parsedate('expiresAt')
+    
+    @property
+    def is_domain(self):
+        return True
+    
+    @property
+    def is_ip(self):
+        return False
+
+
+
+class HistoricalDomainWhois(DomainWhois):
+
+    """Historical Whois record for a domain name."""
+
+    def __new__(cls, record):
+        self = object.__new__(HistoricalDomainWhois)
+        self._domain = record['domain']
+        self._rawrecord = record
+        return self
+    
+    def __str__(self):
+        return '{0.last_seen} registrant: "{0.organization} | 
{0.registrant_name} | {0.registrant_email}"'.format(self)
+
+    def __repr__(self):
+        return "HistoricalDomainWhois<'{0.domain}' @ 
'{0.last_seen}'>".format(self)
+    
+    def _dict_for_df(self, **kwargs):
+        as_d = OrderedDict(last_seen=self.last_seen)
+        as_d.update(super()._dict_for_df(**kwargs))
+        return as_d
+    
+    @property
+    def last_seen(self):
+        """Date the historical record was last seen.
+        
+        Alias for `date_loaded`.
+        :rtype: datetime
+        """
+        return self.date_loaded
 
 
 
@@ -418,3 +489,42 @@
     def ip(self):
         return get_object(self._domain, type='IPAddress')
 
+    @property
+    def is_domain(self):
+        return False
+    
+    @property
+    def is_ip(self):
+        return True
+
+
+
+class HistoricalIPWhois(IPWhois):
+
+    """Historical Whois record for a IP address."""
+
+    def __new__(cls, record):
+        self = object.__new__(HistoricalIPWhois)
+        self._domain = record['domain']
+        self._rawrecord = record
+        return self
+    
+    def __str__(self):
+        return '{0.last_seen} registrant: "{0.organization} | 
{0.registrant_name} | {0.registrant_email}"'.format(self)
+
+    def __repr__(self):
+        return "HistoricalIPWhois<'{0.ip}' @ '{0.last_seen}'>".format(self)
+    
+    def _dict_for_df(self, **kwargs):
+        as_d = OrderedDict(last_seen=self.last_seen)
+        as_d.update(super()._dict_for_df(**kwargs))
+        return as_d
+    
+    @property
+    def last_seen(self):
+        """Date the historical record was last seen.
+        
+        Alias for `date_loaded`.
+        :rtype: datetime
+        """
+        return self.date_loaded
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal.egg-info/PKG-INFO 
new/passivetotal-2.5.9/passivetotal.egg-info/PKG-INFO
--- old/passivetotal-2.5.8/passivetotal.egg-info/PKG-INFO       2022-01-25 
23:37:02.000000000 +0100
+++ new/passivetotal-2.5.9/passivetotal.egg-info/PKG-INFO       2022-03-11 
19:31:27.000000000 +0100
@@ -1,12 +1,12 @@
 Metadata-Version: 2.1
 Name: passivetotal
-Version: 2.5.8
+Version: 2.5.9
 Summary: Library for the RiskIQ PassiveTotal and Illuminate API
 Home-page: https://github.com/passivetotal/python_api
+Download-URL: https://github.com/passivetotal/python_api/archive/master.zip
 Author: RiskIQ
 Author-email: [email protected]
 License: GPLv2
-Download-URL: https://github.com/passivetotal/python_api/archive/master.zip
 Keywords: threats,research,analysis
 Platform: UNKNOWN
 Description-Content-Type: text/markdown
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/passivetotal-2.5.8/passivetotal.egg-info/SOURCES.txt 
new/passivetotal-2.5.9/passivetotal.egg-info/SOURCES.txt
--- old/passivetotal-2.5.8/passivetotal.egg-info/SOURCES.txt    2022-01-25 
23:37:02.000000000 +0100
+++ new/passivetotal-2.5.9/passivetotal.egg-info/SOURCES.txt    2022-03-11 
19:31:28.000000000 +0100
@@ -10,6 +10,7 @@
 docs/getting-started.rst
 docs/illuminate.rst
 docs/index.rst
+docs/requirements.txt
 docs/wrappers.rst
 examples/notebooks/Attack Surface & Vulnerability Intelligence - RiskIQ 
API.ipynb
 examples/notebooks/Cyber Threat Intelligence (CTI) - RiskIQ API.ipynb
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/passivetotal-2.5.8/passivetotal.egg-info/entry_points.txt 
new/passivetotal-2.5.9/passivetotal.egg-info/entry_points.txt
--- old/passivetotal-2.5.8/passivetotal.egg-info/entry_points.txt       
2022-01-25 23:37:02.000000000 +0100
+++ new/passivetotal-2.5.9/passivetotal.egg-info/entry_points.txt       
2022-03-11 19:31:28.000000000 +0100
@@ -2,4 +2,3 @@
 pt-client = passivetotal.cli.client:main
 pt-config = passivetotal.cli.config:main
 pt-info = passivetotal.cli.info:main
-

Reply via email to