diff --git a/.gitignore b/.gitignore index ea8c048..3fcaae9 100644 --- a/.gitignore +++ b/.gitignore @@ -67,9 +67,7 @@ MANIFEST .project .pydevproject .python-version -pylint.txt *.swp -violations.pyflakes.txt /t.py /test*.py diff --git a/minfraud/errors.py b/minfraud/errors.py index 256b41a..6ff5259 100644 --- a/minfraud/errors.py +++ b/minfraud/errors.py @@ -1,6 +1,6 @@ """Typed errors thrown by this library.""" -from typing import Optional +from __future__ import annotations class MinFraudError(RuntimeError): @@ -22,21 +22,21 @@ class HTTPError(MinFraudError): :py:exc:`MinFraudError` and adds attributes of its own. """ - http_status: Optional[int] + http_status: int | None """The HTTP status code returned""" - uri: Optional[str] + uri: str | None """The URI queried""" - decoded_content: Optional[str] + decoded_content: str | None """The decoded response content""" def __init__( self, message: str, - http_status: Optional[int] = None, - uri: Optional[str] = None, - decoded_content: Optional[str] = None, + http_status: int | None = None, + uri: str | None = None, + decoded_content: str | None = None, ) -> None: """Initialize an HTTPError instance.""" super().__init__(message) diff --git a/minfraud/models.py b/minfraud/models.py index 7cc6c3f..33bd6f6 100644 --- a/minfraud/models.py +++ b/minfraud/models.py @@ -1,12 +1,15 @@ """Models for the minFraud response object.""" -# pylint:disable=too-many-lines,too-many-instance-attributes,too-many-locals -from collections.abc import Sequence -from typing import Optional +from __future__ import annotations + +from typing import TYPE_CHECKING import geoip2.models import geoip2.records +if TYPE_CHECKING: + from collections.abc import Sequence + class _Serializable: def __eq__(self, other: object) -> bool: @@ -15,7 +18,7 @@ def __eq__(self, other: object) -> bool: def __ne__(self, other: object) -> bool: return not self.__eq__(other) - def to_dict(self) -> dict: + def to_dict(self) -> dict: # noqa: C901 """Return a dict of the object suitable for serialization.""" result = {} for key, value in self.__dict__.items(): @@ -60,18 +63,18 @@ class IPRiskReason(_Serializable): IP address across minFraud customers. """ - code: Optional[str] + code: str | None """This value is a machine-readable code identifying the reason.""" - reason: Optional[str] + reason: str | None """This property provides a human-readable explanation of the reason. The text may change at any time and should not be matched against.""" def __init__( self, - code: Optional[str] = None, - reason: Optional[str] = None, + code: str | None = None, + reason: str | None = None, ) -> None: """Initialize an IPRiskReason instance.""" self.code = code @@ -84,18 +87,15 @@ class GeoIP2Location(geoip2.records.Location): In addition to the attributes provided by ``geoip2.records.Location``, this class provides the local_time attribute. - Parent: """ - __doc__ += geoip2.records.Location.__doc__ # type: ignore - - local_time: Optional[str] + local_time: str | None """The date and time of the transaction in the time zone associated with the IP address. The value is formatted according to `RFC 3339 `_. For instance, the local time in Boston might be returned as 2015-04-27T19:17:24-04:00.""" - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *args, **kwargs) -> None: # noqa: ANN002 """Initialize a GeoIP2Location instance.""" self.local_time = kwargs.get("local_time") super().__init__(*args, **kwargs) @@ -113,7 +113,7 @@ class IPAddress(geoip2.models.Insights): location: GeoIP2Location """Location object for the requested IP address.""" - risk: Optional[float] + risk: float | None """This field contains the risk associated with the IP address. The value ranges from 0.01 to 99. A higher score indicates a higher risk.""" @@ -124,12 +124,12 @@ class IPAddress(geoip2.models.Insights): def __init__( self, - locales: Optional[Sequence[str]], + locales: Sequence[str] | None, *, - country: Optional[dict] = None, - location: Optional[dict] = None, - risk: Optional[float] = None, - risk_reasons: Optional[list[dict]] = None, + country: dict | None = None, + location: dict | None = None, + risk: float | None = None, + risk_reasons: list[dict] | None = None, **kwargs, ) -> None: """Initialize an IPAddress instance.""" @@ -152,11 +152,11 @@ def __init__( class ScoreIPAddress(_Serializable): """Information about the IP address for minFraud Score.""" - risk: Optional[float] + risk: float | None """This field contains the risk associated with the IP address. The value ranges from 0.01 to 99. A higher score indicates a higher risk.""" - def __init__(self, *, risk: Optional[float] = None, **_) -> None: + def __init__(self, *, risk: float | None = None, **_) -> None: """Initialize a ScoreIPAddress instance.""" self.risk = risk @@ -164,21 +164,21 @@ def __init__(self, *, risk: Optional[float] = None, **_) -> None: class Issuer(_Serializable): """Information about the credit card issuer.""" - name: Optional[str] + name: str | None """The name of the bank which issued the credit card.""" - matches_provided_name: Optional[bool] + matches_provided_name: bool | None """This property is ``True`` if the name matches the name provided in the request for the card issuer. It is ``False`` if the name does not match. The property is ``None`` if either no name or no issuer ID number (IIN) was provided in the request or if MaxMind does not have a name associated with the IIN.""" - phone_number: Optional[str] + phone_number: str | None """The phone number of the bank which issued the credit card. In some cases the phone number we return may be out of date.""" - matches_provided_phone_number: Optional[bool] + matches_provided_phone_number: bool | None """This property is ``True`` if the phone number matches the number provided in the request for the card issuer. It is ``False`` if the number does not match. It is ``None`` if either no @@ -188,10 +188,10 @@ class Issuer(_Serializable): def __init__( self, *, - name: Optional[str] = None, - matches_provided_name: Optional[bool] = None, - phone_number: Optional[str] = None, - matches_provided_phone_number: Optional[bool] = None, + name: str | None = None, + matches_provided_name: bool | None = None, + phone_number: str | None = None, + matches_provided_phone_number: bool | None = None, **_, ) -> None: """Initialize an Issuer instance.""" @@ -209,20 +209,20 @@ class Device(_Serializable): `_. """ - confidence: Optional[float] + confidence: float | None """This number represents our confidence that the ``device_id`` refers to a unique device as opposed to a cluster of similar devices. A confidence of 0.01 indicates very low confidence that the device is unique, whereas 99 indicates very high confidence.""" - id: Optional[str] + id: str | None """A UUID that MaxMind uses for the device associated with this IP address.""" - last_seen: Optional[str] + last_seen: str | None """This is the date and time of the last sighting of the device. This is an RFC 3339 date-time.""" - local_time: Optional[str] + local_time: str | None """This is the local date and time of the transaction in the time zone of the device. This is determined by using the UTC offset associated with the device. This is an RFC 3339 date-time.""" @@ -230,11 +230,10 @@ class Device(_Serializable): def __init__( self, *, - confidence: Optional[float] = None, - # pylint:disable=redefined-builtin - id: Optional[str] = None, - last_seen: Optional[str] = None, - local_time: Optional[str] = None, + confidence: float | None = None, + id: str | None = None, + last_seen: str | None = None, + local_time: str | None = None, **_, ) -> None: """Initialize a Device instance.""" @@ -251,18 +250,18 @@ class Disposition(_Serializable): rules. """ - action: Optional[str] + action: str | None """The action to take on the transaction as defined by your custom rules. The current set of values are "accept", "manual_review", "reject", and "test". If you do not have custom rules set up, ``None`` will be returned.""" - reason: Optional[str] + reason: str | None """The reason for the action. The current possible values are "custom_rule" and "default". If you do not have custom rules set up, ``None`` will be returned.""" - rule_label: Optional[str] + rule_label: str | None """The label of the custom rule that was triggered. If you do not have custom rules set up, the triggered custom rule does not have a label, or no custom rule was triggered, ``None`` will be returned.""" @@ -270,9 +269,9 @@ class Disposition(_Serializable): def __init__( self, *, - action: Optional[str] = None, - reason: Optional[str] = None, - rule_label: Optional[str] = None, + action: str | None = None, + reason: str | None = None, + rule_label: str | None = None, **_, ) -> None: """Initialize a Disposition instance.""" @@ -284,12 +283,12 @@ def __init__( class EmailDomain(_Serializable): """Information about the email domain passed in the request.""" - first_seen: Optional[str] + first_seen: str | None """A date string (e.g. 2017-04-24) to identify the date an email domain was first seen by MaxMind. This is expressed using the ISO 8601 date format.""" - def __init__(self, *, first_seen: Optional[str] = None, **_) -> None: + def __init__(self, *, first_seen: str | None = None, **_) -> None: """Initialize an EmailDomain instance.""" self.first_seen = first_seen @@ -300,32 +299,32 @@ class Email(_Serializable): domain: EmailDomain """An object containing information about the email domain.""" - first_seen: Optional[str] + first_seen: str | None """A date string (e.g. 2017-04-24) to identify the date an email address was first seen by MaxMind. This is expressed using the ISO 8601 date format.""" - is_disposable: Optional[bool] + is_disposable: bool | None """This field indicates whether the email is from a disposable email provider. It will be ``None`` when an email address was not passed in the inputs.""" - is_free: Optional[bool] + is_free: bool | None """This field is true if MaxMind believes that this email is hosted by a free email provider such as Gmail or Yahoo! Mail.""" - is_high_risk: Optional[bool] + is_high_risk: bool | None """This field is true if MaxMind believes that this email is likely to be used for fraud. Note that this is also factored into the overall `risk_score` in the response as well.""" def __init__( self, - domain: Optional[dict] = None, - first_seen: Optional[str] = None, - is_disposable: Optional[bool] = None, - is_free: Optional[bool] = None, - is_high_risk: Optional[bool] = None, + domain: dict | None = None, + first_seen: str | None = None, + is_disposable: bool | None = None, + is_free: bool | None = None, + is_high_risk: bool | None = None, ) -> None: """Initialize an Email instance.""" self.domain = EmailDomain(**(domain or {})) @@ -341,7 +340,7 @@ class CreditCard(_Serializable): issuer: Issuer """An object containing information about the credit card issuer.""" - country: Optional[str] + country: str | None """This property contains the `ISO 3166-1 alpha-2 country code `_ associated with the location of the majority of customers using this credit card as @@ -349,40 +348,39 @@ class CreditCard(_Serializable): customers is highly mixed, this defaults to the country of the bank issuing the card.""" - brand: Optional[str] + brand: str | None """The card brand, such as "Visa", "Discover", "American Express", etc.""" - is_business: Optional[bool] + is_business: bool | None """This property is ``True`` if the card is a business card.""" - is_issued_in_billing_address_country: Optional[bool] + is_issued_in_billing_address_country: bool | None """This property is true if the country of the billing address matches the country of the majority of customers using this credit card. In cases where the location of customers is highly mixed, the match is to the country of the bank issuing the card.""" - is_prepaid: Optional[bool] + is_prepaid: bool | None """This property is ``True`` if the card is a prepaid card.""" - is_virtual: Optional[bool] + is_virtual: bool | None """This property is ``True`` if the card is a virtual card.""" - type: Optional[str] + type: str | None """The card's type. The valid values are "charge", "credit", and "debit". See Wikipedia for an explanation of the difference between charge and credit cards.""" def __init__( self, - issuer: Optional[dict] = None, - country: Optional[str] = None, - brand: Optional[str] = None, - is_business: Optional[bool] = None, - is_issued_in_billing_address_country: Optional[bool] = None, - is_prepaid: Optional[bool] = None, - is_virtual: Optional[bool] = None, - # pylint:disable=redefined-builtin - type: Optional[str] = None, # noqa: A002 + issuer: dict | None = None, + country: str | None = None, + brand: str | None = None, + is_business: bool | None = None, + is_issued_in_billing_address_country: bool | None = None, + is_prepaid: bool | None = None, + is_virtual: bool | None = None, + type: str | None = None, # noqa: A002 ) -> None: """Initialize a CreditCard instance.""" self.issuer = Issuer(**(issuer or {})) @@ -398,24 +396,24 @@ def __init__( class BillingAddress(_Serializable): """Information about the billing address.""" - is_postal_in_city: Optional[bool] + is_postal_in_city: bool | None """This property is ``True`` if the postal code provided with the address is in the city for the address. The property is ``False`` when the postal code is not in the city. If the address was not provided, could not be parsed, or was outside USA, the property will be ``None``.""" - latitude: Optional[float] + latitude: float | None """The latitude associated with the address.""" - longitude: Optional[float] + longitude: float | None """The longitude associated with the address.""" - distance_to_ip_location: Optional[int] + distance_to_ip_location: int | None """The distance in kilometers from the address to the IP location.""" - is_in_ip_country: Optional[bool] + is_in_ip_country: bool | None """This property is ``True`` if the address is in the IP country. The property is ``False`` when the address is not in the IP country. If the address could not be parsed or was not provided or if the @@ -424,11 +422,11 @@ class BillingAddress(_Serializable): def __init__( self, *, - is_postal_in_city: Optional[bool] = None, - latitude: Optional[float] = None, - longitude: Optional[float] = None, - distance_to_ip_location: Optional[int] = None, - is_in_ip_country: Optional[bool] = None, + is_postal_in_city: bool | None = None, + latitude: float | None = None, + longitude: float | None = None, + distance_to_ip_location: int | None = None, + is_in_ip_country: bool | None = None, **_, ) -> None: """Initialize a BillingAddress instance.""" @@ -442,48 +440,48 @@ def __init__( class ShippingAddress(_Serializable): """Information about the shipping address.""" - is_postal_in_city: Optional[bool] + is_postal_in_city: bool | None """This property is ``True`` if the postal code provided with the address is in the city for the address. The property is ``False`` when the postal code is not in the city. If the address was not provided, could not be parsed, or was not in USA, the property will be ``None``.""" - latitude: Optional[float] + latitude: float | None """The latitude associated with the address.""" - longitude: Optional[float] + longitude: float | None """The longitude associated with the address.""" - distance_to_ip_location: Optional[int] + distance_to_ip_location: int | None """The distance in kilometers from the address to the IP location.""" - is_in_ip_country: Optional[bool] + is_in_ip_country: bool | None """This property is ``True`` if the address is in the IP country. The property is ``False`` when the address is not in the IP country. If the address could not be parsed or was not provided or if the IP address could not be geolocated, the property will be ``None``.""" - is_high_risk: Optional[bool] + is_high_risk: bool | None """This property is ``True`` if the shipping address is high risk. If the address could not be parsed or was not provided, the property is ``None``.""" - distance_to_billing_address: Optional[int] + distance_to_billing_address: int | None """The distance in kilometers from the shipping address to billing address.""" def __init__( self, *, - is_postal_in_city: Optional[bool] = None, - latitude: Optional[float] = None, - longitude: Optional[float] = None, - distance_to_ip_location: Optional[int] = None, - is_in_ip_country: Optional[bool] = None, - is_high_risk: Optional[bool] = None, - distance_to_billing_address: Optional[int] = None, + is_postal_in_city: bool | None = None, + latitude: float | None = None, + longitude: float | None = None, + distance_to_ip_location: int | None = None, + is_in_ip_country: bool | None = None, + is_high_risk: bool | None = None, + distance_to_billing_address: int | None = None, **_, ) -> None: """Initialize a ShippingAddress instance.""" @@ -499,17 +497,17 @@ def __init__( class Phone(_Serializable): """Information about the billing or shipping phone number.""" - country: Optional[str] + country: str | None """The two-character ISO 3166-1 country code for the country associated with the phone number.""" - is_voip: Optional[bool] + is_voip: bool | None """This property is ``True`` if the phone number is a Voice over Internet Protocol (VoIP) number allocated by a regulator. The property is ``False`` when the number is not VoIP. If the phone number was not provided or we do not have data for it, the property will be ``None``.""" - matches_postal: Optional[bool] + matches_postal: bool | None """This is ```True``` if the phone number's prefix is commonly associated with the postal code. It is ```False``` if the prefix is not associated with the postal code. It is non-```None``` only when the phone @@ -517,24 +515,24 @@ class Phone(_Serializable): postal code and country are provided in the request. """ - network_operator: Optional[str] + network_operator: str | None """The name of the original network operator associated with the phone number. This field does not reflect phone numbers that have been ported from the original operator to another, nor does it identify mobile virtual network operators.""" - number_type: Optional[str] + number_type: str | None """One of the following values: fixed or mobile. Additional values may be added in the future.""" def __init__( self, *, - country: Optional[str] = None, - is_voip: Optional[bool] = None, - matches_postal: Optional[bool] = None, - network_operator: Optional[str] = None, - number_type: Optional[str] = None, + country: str | None = None, + is_voip: bool | None = None, + matches_postal: bool | None = None, + network_operator: str | None = None, + number_type: str | None = None, **_, ) -> None: """Initialize a Phone instance.""" @@ -548,18 +546,18 @@ def __init__( class ServiceWarning(_Serializable): """Warning from the web service.""" - code: Optional[str] + code: str | None """This value is a machine-readable code identifying the warning. See the `response warnings documentation `_ for the current list of of warning codes.""" - warning: Optional[str] + warning: str | None """This property provides a human-readable explanation of the warning. The description may change at any time and should not be matched against.""" - input_pointer: Optional[str] + input_pointer: str | None """This is a string representing the path to the input that the warning is associated with. For instance, if the warning was about the billing city, the string would be ``"/billing/city"``.""" @@ -567,9 +565,9 @@ class ServiceWarning(_Serializable): def __init__( self, *, - code: Optional[str] = None, - warning: Optional[str] = None, - input_pointer: Optional[str] = None, + code: str | None = None, + warning: str | None = None, + input_pointer: str | None = None, **_, ) -> None: """Initialize a ServiceWarning instance.""" @@ -585,61 +583,61 @@ class Subscores(_Serializable): Use RiskScoreReason instead. """ - avs_result: Optional[float] + avs_result: float | None """The risk associated with the AVS result. If present, this is a value in the range 0.01 to 99.""" - billing_address: Optional[float] + billing_address: float | None """The risk associated with the billing address. If present, this is a value in the range 0.01 to 99.""" - billing_address_distance_to_ip_location: Optional[float] + billing_address_distance_to_ip_location: float | None """The risk associated with the distance between the billing address and the location for the given IP address. If present, this is a value in the range 0.01 to 99.""" - browser: Optional[float] + browser: float | None """The risk associated with the browser attributes such as the User-Agent and Accept-Language. If present, this is a value in the range 0.01 to 99.""" - chargeback: Optional[float] + chargeback: float | None """Individualized risk of chargeback for the given IP address given for your account and any shop ID passed. This is only available to users sending chargeback data to MaxMind. If present, this is a value in the range 0.01 to 99.""" - country: Optional[float] + country: float | None """The risk associated with the country the transaction originated from. If present, this is a value in the range 0.01 to 99.""" - country_mismatch: Optional[float] + country_mismatch: float | None """The risk associated with the combination of IP country, card issuer country, billing country, and shipping country. If present, this is a value in the range 0.01 to 99.""" - cvv_result: Optional[float] + cvv_result: float | None """The risk associated with the CVV result. If present, this is a value in the range 0.01 to 99.""" - device: Optional[float] + device: float | None """The risk associated with the device. If present, this is a value in the range 0.01 to 99.""" - email_address: Optional[float] + email_address: float | None """The risk associated with the particular email address. If present, this is a value in the range 0.01 to 99.""" - email_domain: Optional[float] + email_domain: float | None """The general risk associated with the email domain. If present, this is a value in the range 0.01 to 99.""" - email_local_part: Optional[float] + email_local_part: float | None """The risk associated with the email address local part (the part of the email address before the @ symbol). If present, this is a value in the range 0.01 to 99.""" - email_tenure: Optional[float] + email_tenure: float | None """The risk associated with the issuer ID number on the email domain. If present, this is a value in the range 0.01 to 99. @@ -652,7 +650,7 @@ class Subscores(_Serializable): :py:attr:`minfraud.models.Subscores.email_address` """ - ip_tenure: Optional[float] + ip_tenure: float | None """The risk associated with the issuer ID number on the IP address. If present, this is a value in the range 0.01 to 99. @@ -665,55 +663,55 @@ class Subscores(_Serializable): :py:attr:`minfraud.models.Score.risk_score` """ - issuer_id_number: Optional[float] + issuer_id_number: float | None """The risk associated with the particular issuer ID number (IIN) given the billing location and the history of usage of the IIN on your account and shop ID. If present, this is a value in the range 0.01 to 99.""" - order_amount: Optional[float] + order_amount: float | None """The risk associated with the particular order amount for your account and shop ID. If present, this is a value in the range 0.01 to 99.""" - phone_number: Optional[float] + phone_number: float | None """The risk associated with the particular phone number. If present, this is a value in the range 0.01 to 99.""" - shipping_address: Optional[float] + shipping_address: float | None """The risk associated with the shipping address. If present, this is a value in the range 0.01 to 99.""" - shipping_address_distance_to_ip_location: Optional[float] + shipping_address_distance_to_ip_location: float | None """The risk associated with the distance between the shipping address and the location for the given IP address. If present, this is a value in the range 0.01 to 99.""" - time_of_day: Optional[float] + time_of_day: float | None """The risk associated with the local time of day of the transaction in the IP address location. If present, this is a value in the range 0.01 to 99.""" def __init__( self, *, - avs_result: Optional[float] = None, - billing_address: Optional[float] = None, - billing_address_distance_to_ip_location: Optional[float] = None, - browser: Optional[float] = None, - chargeback: Optional[float] = None, - country: Optional[float] = None, - country_mismatch: Optional[float] = None, - cvv_result: Optional[float] = None, - device: Optional[float] = None, - email_address: Optional[float] = None, - email_domain: Optional[float] = None, - email_local_part: Optional[float] = None, - email_tenure: Optional[float] = None, - ip_tenure: Optional[float] = None, - issuer_id_number: Optional[float] = None, - order_amount: Optional[float] = None, - phone_number: Optional[float] = None, - shipping_address: Optional[float] = None, - shipping_address_distance_to_ip_location: Optional[float] = None, - time_of_day: Optional[float] = None, + avs_result: float | None = None, + billing_address: float | None = None, + billing_address_distance_to_ip_location: float | None = None, + browser: float | None = None, + chargeback: float | None = None, + country: float | None = None, + country_mismatch: float | None = None, + cvv_result: float | None = None, + device: float | None = None, + email_address: float | None = None, + email_domain: float | None = None, + email_local_part: float | None = None, + email_tenure: float | None = None, + ip_tenure: float | None = None, + issuer_id_number: float | None = None, + order_amount: float | None = None, + phone_number: float | None = None, + shipping_address: float | None = None, + shipping_address_distance_to_ip_location: float | None = None, + time_of_day: float | None = None, **_, ) -> None: """Initialize a Subscores instance.""" @@ -751,9 +749,9 @@ class Reason(_Serializable): See the `risk reasons documentation `_ for the current list of reason codes. - """ # pylint:disable=line-too-long # noqa: E501 + """ - code: Optional[str] + code: str | None """This value is a machine-readable code identifying the reason. Although more codes_ may be added in the future, the current codes are: @@ -818,7 +816,7 @@ class Reason(_Serializable): /#schema--response--risk-score-reason--multiplier-reason """ - reason: Optional[str] + reason: str | None """This property provides a human-readable explanation of the reason. The text may change at any time and should not be matched against.""" @@ -826,8 +824,8 @@ class Reason(_Serializable): def __init__( self, *, - code: Optional[str] = None, - reason: Optional[str] = None, + code: str | None = None, + reason: str | None = None, **_, ) -> None: """Initialize a Reason instance.""" @@ -852,7 +850,7 @@ def __init__( self, *, multiplier: float, - reasons: Optional[list] = None, + reasons: list | None = None, **_, ) -> None: """Initialize a RiskScoreReason instance.""" @@ -945,23 +943,22 @@ def __init__( self, locales: Sequence[str], *, - billing_address: Optional[dict] = None, - billing_phone: Optional[dict] = None, - credit_card: Optional[dict] = None, - disposition: Optional[dict] = None, + billing_address: dict | None = None, + billing_phone: dict | None = None, + credit_card: dict | None = None, + disposition: dict | None = None, funds_remaining: float, - device: Optional[dict] = None, - email: Optional[dict] = None, - # pylint:disable=redefined-builtin + device: dict | None = None, + email: dict | None = None, id: str, - ip_address: Optional[dict] = None, + ip_address: dict | None = None, queries_remaining: int, risk_score: float, - shipping_address: Optional[dict] = None, - shipping_phone: Optional[dict] = None, - subscores: Optional[dict] = None, - warnings: Optional[list[dict]] = None, - risk_score_reasons: Optional[list[dict]] = None, + shipping_address: dict | None = None, + shipping_phone: dict | None = None, + subscores: dict | None = None, + warnings: list[dict] | None = None, + risk_score_reasons: list[dict] | None = None, **_, ) -> None: """Initialize a Factors instance.""" @@ -1054,21 +1051,20 @@ def __init__( self, locales: Sequence[str], *, - billing_address: Optional[dict] = None, - billing_phone: Optional[dict] = None, - credit_card: Optional[dict] = None, - device: Optional[dict] = None, - disposition: Optional[dict] = None, - email: Optional[dict] = None, + billing_address: dict | None = None, + billing_phone: dict | None = None, + credit_card: dict | None = None, + device: dict | None = None, + disposition: dict | None = None, + email: dict | None = None, funds_remaining: float, - # pylint:disable=redefined-builtin id: str, - ip_address: Optional[dict] = None, + ip_address: dict | None = None, queries_remaining: int, risk_score: float, - shipping_address: Optional[dict] = None, - shipping_phone: Optional[dict] = None, - warnings: Optional[list[dict]] = None, + shipping_address: dict | None = None, + shipping_phone: dict | None = None, + warnings: list[dict] | None = None, **_, ) -> None: """Initialize an Insights instance.""" @@ -1127,14 +1123,13 @@ class Score(_Serializable): def __init__( self, *, - disposition: Optional[dict] = None, + disposition: dict | None = None, funds_remaining: float, - # pylint:disable=redefined-builtin id: str, - ip_address: Optional[dict] = None, + ip_address: dict | None = None, queries_remaining: int, risk_score: float, - warnings: Optional[list[dict]] = None, + warnings: list[dict] | None = None, **_, ) -> None: """Initialize a Score instance.""" diff --git a/minfraud/request.py b/minfraud/request.py index 4d4e1ef..42dc268 100644 --- a/minfraud/request.py +++ b/minfraud/request.py @@ -5,11 +5,13 @@ """ +from __future__ import annotations + import hashlib import re import unicodedata import warnings -from typing import Any, Optional +from typing import Any from voluptuous import MultipleInvalid @@ -264,7 +266,10 @@ } -def prepare_report(request: dict[str, Any], validate: bool) -> dict[str, Any]: +def prepare_report( + request: dict[str, Any], + validate: bool, # noqa: FBT001 +) -> dict[str, Any]: """Validate and prepare minFraud report.""" cleaned_request = _copy_and_clean(request) if validate: @@ -278,8 +283,8 @@ def prepare_report(request: dict[str, Any], validate: bool) -> dict[str, Any]: def prepare_transaction( request: dict[str, Any], - validate: bool, - hash_email: bool, + validate: bool, # noqa: FBT001 + hash_email: bool, # noqa: FBT001 ) -> dict[str, Any]: """Validate and prepare minFraud transaction.""" cleaned_request = _copy_and_clean(request) @@ -299,7 +304,7 @@ def prepare_transaction( return cleaned_request -def _copy_and_clean(data: Any) -> Any: +def _copy_and_clean(data: Any) -> Any: # noqa: ANN401 """Create a copy of the data structure with Nones removed.""" if isinstance(data, dict): return {k: _copy_and_clean(v) for (k, v) in data.items() if v is not None} @@ -338,7 +343,7 @@ def maybe_hash_email(transaction: dict[str, Any]) -> None: if domain != "" and "domain" not in email: email["domain"] = domain - email["address"] = hashlib.md5(address.encode("UTF-8")).hexdigest() + email["address"] = hashlib.md5(address.encode("UTF-8")).hexdigest() # noqa: S324 def _clean_domain(domain: str) -> str: @@ -358,7 +363,7 @@ def _clean_domain(domain: str) -> str: return _EQUIVALENT_DOMAINS.get(domain, domain) -def _clean_email(address: str) -> tuple[Optional[str], Optional[str]]: +def _clean_email(address: str) -> tuple[str | None, str | None]: address = address.lower().strip() at_idx = address.rfind("@") diff --git a/minfraud/validation.py b/minfraud/validation.py index 4b44653..b4fafac 100644 --- a/minfraud/validation.py +++ b/minfraud/validation.py @@ -7,12 +7,13 @@ """ +from __future__ import annotations + import ipaddress import re import urllib.parse import uuid from decimal import Decimal -from typing import Optional from email_validator import validate_email from voluptuous import ( @@ -28,12 +29,6 @@ ) from voluptuous.error import UrlInvalid -# Pylint doesn't like the private function type naming for the callable -# objects below. Given the consistent use of them, the current names seem -# preferable to blindly following pylint. -# -# pylint: disable=invalid-name,undefined-variable - _any_number = Any(float, int, Decimal) _custom_input_key = All(str, Match(r"^[a-z0-9_]{1,25}$")) @@ -59,7 +54,7 @@ _subdivision_iso_code = All(str, Match(r"^[0-9A-Z]{1,4}$")) -def _ip_address(s: Optional[str]) -> str: +def _ip_address(s: str | None) -> str: # ipaddress accepts numeric IPs, which we don't want. if isinstance(s, str) and not re.match(r"^\d+$", s): return str(ipaddress.ip_address(s)) @@ -378,7 +373,7 @@ def _uri(s: str) -> str: ) -def _maxmind_id(s: Optional[str]) -> str: +def _maxmind_id(s: str | None) -> str: if isinstance(s, str) and len(s) == 8: return s raise ValueError @@ -404,7 +399,7 @@ def _non_empty_uuid(s: str) -> str: return s -def _transaction_id(s: Optional[str]) -> str: +def _transaction_id(s: str | None) -> str: if isinstance(s, str) and len(s) > 0: return s raise ValueError @@ -423,7 +418,7 @@ def _transaction_id(s: Optional[str]) -> str: ) -def _validate_at_least_one_identifier_field(report) -> bool: +def _validate_at_least_one_identifier_field(report: dict) -> bool: optional_fields = ["ip_address", "maxmind_id", "minfraud_id", "transaction_id"] if not any(field in report for field in optional_fields): # We return MultipleInvalid instead of ValueError to be consistent with what @@ -442,7 +437,7 @@ def _validate_at_least_one_identifier_field(report) -> bool: return True -def validate_report(report) -> bool: +def validate_report(report: dict) -> bool: """Validate minFraud Transaction Report fields.""" _validate_report_schema(report) _validate_at_least_one_identifier_field(report) diff --git a/minfraud/webservice.py b/minfraud/webservice.py index c17a22a..1678bea 100644 --- a/minfraud/webservice.py +++ b/minfraud/webservice.py @@ -42,7 +42,6 @@ _SCHEME = "https" -# pylint: disable=too-many-instance-attributes class BaseClient: """Base class for minFraud clients.""" @@ -164,7 +163,7 @@ def _exception_for_4xx_status( ) return HTTPError( "Error response contains JSON but it does not " - + f"specify code or error keys: {raw_body}", + f"specify code or error keys: {raw_body}", status, uri, raw_body, @@ -232,7 +231,7 @@ class AsyncClient(BaseClient): _existing_session: aiohttp.ClientSession _proxy: str | None - def __init__( + def __init__( # noqa: PLR0913 self, account_id: int, license_key: str, @@ -267,8 +266,8 @@ def __init__( async def factors( self, transaction: dict[str, Any], - validate: bool = True, - hash_email: bool = False, + validate: bool = True, # noqa: FBT001, FBT002 + hash_email: bool = False, # noqa: FBT001, FBT002 ) -> Factors: """Query Factors endpoint with transaction data. @@ -306,8 +305,8 @@ async def factors( async def insights( self, transaction: dict[str, Any], - validate: bool = True, - hash_email: bool = False, + validate: bool = True, # noqa: FBT001, FBT002 + hash_email: bool = False, # noqa: FBT001, FBT002 ) -> Insights: """Query Insights endpoint with transaction data. @@ -345,8 +344,8 @@ async def insights( async def score( self, transaction: dict[str, Any], - validate: bool = True, - hash_email: bool = False, + validate: bool = True, # noqa: FBT001, FBT002 + hash_email: bool = False, # noqa: FBT001, FBT002 ) -> Score: """Query Score endpoint with transaction data. @@ -384,7 +383,7 @@ async def score( async def report( self, report: dict[str, str | None], - validate: bool = True, + validate: bool = True, # noqa: FBT001, FBT002 ) -> None: """Send a transaction report to the Report Transaction endpoint. @@ -418,8 +417,8 @@ async def _response_for( uri: str, model_class: Callable, request: dict[str, Any], - validate: bool, - hash_email: bool, + validate: bool, # noqa: FBT001 + hash_email: bool, # noqa: FBT001 ) -> Score | Factors | Insights: """Send request and create response object.""" prepared_request = prepare_transaction(request, validate, hash_email) @@ -476,7 +475,7 @@ class Client(BaseClient): _proxies: dict[str, str] | None _session: requests.Session - def __init__( + def __init__( # noqa: PLR0913 self, account_id: int, license_key: str, @@ -525,8 +524,8 @@ def __init__( def factors( self, transaction: dict[str, Any], - validate: bool = True, - hash_email: bool = False, + validate: bool = True, # noqa: FBT001, FBT002 + hash_email: bool = False, # noqa: FBT001, FBT002 ) -> Factors: """Query Factors endpoint with transaction data. @@ -564,8 +563,8 @@ def factors( def insights( self, transaction: dict[str, Any], - validate: bool = True, - hash_email: bool = False, + validate: bool = True, # noqa: FBT001, FBT002 + hash_email: bool = False, # noqa: FBT001, FBT002 ) -> Insights: """Query Insights endpoint with transaction data. @@ -603,8 +602,8 @@ def insights( def score( self, transaction: dict[str, Any], - validate: bool = True, - hash_email: bool = False, + validate: bool = True, # noqa: FBT001, FBT002 + hash_email: bool = False, # noqa: FBT001, FBT002 ) -> Score: """Query Score endpoint with transaction data. @@ -639,7 +638,11 @@ def score( ), ) - def report(self, report: dict[str, str | None], validate: bool = True) -> None: + def report( + self, + report: dict[str, str | None], + validate: bool = True, # noqa: FBT001, FBT002 + ) -> None: """Send a transaction report to the Report Transaction endpoint. :param report: A dictionary containing the transaction report to be sent @@ -672,8 +675,8 @@ def _response_for( uri: str, model_class: Callable, request: dict[str, Any], - validate: bool, - hash_email: bool, + validate: bool, # noqa: FBT001 + hash_email: bool, # noqa: FBT001 ) -> Score | Factors | Insights: """Send request and create response object.""" prepared_request = prepare_transaction(request, validate, hash_email) diff --git a/pyproject.toml b/pyproject.toml index e8910ca..041c452 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,10 +38,7 @@ dev = [ "pytest-httpserver>=1.0.10", ] lint = [ - "black>=25.1.0", - "flake8>=7.2.0", "mypy>=1.15.0", - "pylint>=3.3.6", "ruff>=0.11.6", "types-requests>=2.32.0.20250328", ] @@ -59,25 +56,23 @@ requires = [ ] build-backend = "setuptools.build_meta" -[tool.black] -# src is showing up in our GitHub linting builds. It seems to -# contain deps. -extend-exclude = '^/src/' - [tool.ruff.lint] select = ["ALL"] ignore = [ # Skip type annotation on **_ "ANN003", + # Redundant as the formatter handles missing trailing commas. + "COM812", + # documenting magic methods "D105", - # Line length. We let black handle this for now. - "E501", + # Conflicts with D211 + "D203", - # Don't bother with future imports for type annotations - "FA100", + # Conflicts with D212 + "D213", # Magic numbers for HTTP status codes seem ok most of the time. "PLR2004", @@ -91,7 +86,8 @@ ignore = [ ignorelist = ["id"] [tool.ruff.lint.per-file-ignores] -"minfraud/models,.py" = [ "D107", "PLR0913" ] +"docs/*" = ["ALL"] +"minfraud/models.py" = [ "PLR0913" ] "tests/*" = ["ANN201", "D"] [tool.setuptools.package-data] @@ -124,10 +120,9 @@ dependency_groups = [ "lint", ] commands = [ - ["black", "--check", "--diff", "."], - ["flake8", "minfraud"], ["mypy", "minfraud", "tests"], - ["pylint", "minfraud"], + ["ruff", "check"], + ["ruff", "format", "--check", "--diff", "."], ] [tool.tox.gh.python] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index a8d4688..0000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -# black uses 88 : ¯\_(ツ)_/¯ -max-line-length = 88 diff --git a/tests/test_models.py b/tests/test_models.py index 563af97..52fd805 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -150,11 +150,13 @@ def test_ip_address(self) -> None: risk_reasons=[ { "code": "ANONYMOUS_IP", - "reason": "The IP address belongs to an anonymous network. See /ip_address/traits for more details.", + "reason": "The IP address belongs to an anonymous network. " + "See /ip_address/traits for more details.", }, { "code": "MINFRAUD_NETWORK_ACTIVITY", - "reason": "Suspicious activity has been seen on this IP address across minFraud customers.", + "reason": "Suspicious activity has been seen on this IP address " + "across minFraud customers.", }, ], traits={ @@ -187,13 +189,15 @@ def test_ip_address(self) -> None: self.assertEqual("ANONYMOUS_IP", address.risk_reasons[0].code) self.assertEqual( - "The IP address belongs to an anonymous network. See /ip_address/traits for more details.", + "The IP address belongs to an anonymous network. " + "See /ip_address/traits for more details.", address.risk_reasons[0].reason, ) self.assertEqual("MINFRAUD_NETWORK_ACTIVITY", address.risk_reasons[1].code) self.assertEqual( - "Suspicious activity has been seen on this IP address across minFraud customers.", + "Suspicious activity has been seen on this IP address " + "across minFraud customers.", address.risk_reasons[1].reason, ) diff --git a/tests/test_validation.py b/tests/test_validation.py index 9b15e68..206d753 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -2,7 +2,7 @@ import unittest from decimal import Decimal -from typing import Any +from typing import Any, Callable from voluptuous import MultipleInvalid @@ -29,21 +29,21 @@ def check_transaction(self, transaction: dict[str, Any]) -> None: except MultipleInvalid as e: self.fail(f"MultipleInvalid {e.msg} thrown for {transaction}") - def check_transaction_str_type(self, object: str, key: str) -> None: - self.check_transaction({object: {key: "string"}}) - self.check_invalid_transaction({object: {key: 12}}) + def check_transaction_str_type(self, obj: str, key: str) -> None: + self.check_transaction({obj: {key: "string"}}) + self.check_invalid_transaction({obj: {key: 12}}) - def check_positive_number(self, f) -> None: + def check_positive_number(self, f: Callable) -> None: for good in (1, 1.1, Decimal("1.1")): self.check_transaction(f(good)) for bad in ("1.2", "1", -1, -1.1, 0): self.check_invalid_transaction(f(bad)) - def check_bool(self, object: str, key: str) -> None: + def check_bool(self, obj: str, key: str) -> None: for good in (True, False): - self.check_transaction({object: {key: good}}) + self.check_transaction({obj: {key: good}}) for bad in ("", 0, "True"): - self.check_invalid_transaction({object: {key: bad}}) + self.check_invalid_transaction({obj: {key: bad}}) def setup_report(self, report: dict[str, Any]) -> None: if "ip_address" not in report: diff --git a/tests/test_webservice.py b/tests/test_webservice.py index 4b8ea1e..ca2ddae 100644 --- a/tests/test_webservice.py +++ b/tests/test_webservice.py @@ -45,16 +45,19 @@ def setUp(self) -> None: "abcdef123456", host=f"{self.httpserver.host}:{self.httpserver.port}", ) - test_dir = os.path.join(os.path.dirname(__file__), "data") - with builtins.open( - os.path.join(test_dir, self.request_file), + test_dir = os.path.join( # noqa: PTH118 + os.path.dirname(__file__), # noqa: PTH120 + "data", + ) + with builtins.open( # noqa: PTH123 + os.path.join(test_dir, self.request_file), # noqa: PTH118 encoding="utf-8", ) as file: content = file.read() self.full_request = json.loads(content) - with builtins.open( - os.path.join(test_dir, self.response_file), + with builtins.open( # noqa: PTH123 + os.path.join(test_dir, self.response_file), # noqa: PTH118 encoding="utf-8", ) as file: self.response = file.read() @@ -146,7 +149,9 @@ def create_error( content_type = ( "application/json" if self.type == "report" - else "application/vnd.maxmind.com-error+json; charset=UTF-8; version=2.0" + else ( + "application/vnd.maxmind.com-error+json; charset=UTF-8; version=2.0" + ) ) self.httpserver.expect_request(uri, method="POST").respond_with_data( text, @@ -175,7 +180,10 @@ def create_success( status = 204 if self.type == "report" else 200 self.httpserver.expect_request(uri, method="POST").respond_with_data( response, - content_type=f"application/vnd.maxmind.com-minfraud-{self.type}+json; charset=UTF-8; version=2.0", + content_type=( + f"application/vnd.maxmind.com-minfraud-{self.type}+json; " + "charset=UTF-8; version=2.0" + ), status=status, ) if client is None: @@ -183,7 +191,7 @@ def create_success( return self.run_client(getattr(client, self.type)(request)) - def run_client(self, v): + def run_client(self, v): # noqa: ANN001 return v def test_named_constructor_args(self) -> None: @@ -193,15 +201,15 @@ def test_named_constructor_args(self) -> None: self.client_class(account_id=id, license_key=key), self.client_class(account_id=id, license_key=key), ): - self.assertEqual(client._account_id, str(id)) - self.assertEqual(client._license_key, key) + self.assertEqual(client._account_id, str(id)) # noqa: SLF001 + self.assertEqual(client._license_key, key) # noqa: SLF001 def test_missing_constructor_args(self) -> None: with self.assertRaises(TypeError): self.client_class(license_key="1234567890ab") # type: ignore[call-arg] with self.assertRaises(TypeError): - self.client_class("47") # type: ignore + self.client_class("47") # type: ignore[arg-type,call-arg] class BaseTransactionTest(BaseTest): @@ -256,7 +264,10 @@ def test_200_with_email_hashing(self) -> None: }, ).respond_with_data( self.response, - content_type=f"application/vnd.maxmind.com-minfraud-{self.type}+json; charset=UTF-8; version=2.0", + content_type=( + f"application/vnd.maxmind.com-minfraud-{self.type}+json; " + "charset=UTF-8; version=2.0" + ), status=200, ) @@ -317,8 +328,7 @@ def test_200_with_no_risk_score_reasons(self) -> None: def test_200_with_no_body(self) -> None: with self.assertRaisesRegex( MinFraudError, - "Received a 200 response but could not decode the response as" - " JSON: b?'?'?", + "Received a 200 response but could not decode the response as JSON: b?'?'?", ): self.create_success(text="") @@ -386,11 +396,11 @@ def setUp(self) -> None: super().setUp() def tearDown(self) -> None: - self._loop.run_until_complete(self.client.close()) # type: ignore + self._loop.run_until_complete(self.client.close()) # type: ignore[attr-defined] self._loop.close() super().tearDown() - def run_client(self, v): + def run_client(self, v): # noqa: ANN001 return self._loop.run_until_complete(v) diff --git a/uv.lock b/uv.lock index 7264412..bbcaaaa 100644 --- a/uv.lock +++ b/uv.lock @@ -126,18 +126,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" }, ] -[[package]] -name = "astroid" -version = "3.3.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/39/33/536530122a22a7504b159bccaf30a1f76aa19d23028bd8b5009eb9b2efea/astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550", size = 398731, upload-time = "2025-03-09T11:54:36.388Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/80/c749efbd8eef5ea77c7d6f1956e8fbfb51963b7f93ef79647afd4d9886e3/astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248", size = 275339, upload-time = "2025-03-09T11:54:34.489Z" }, -] - [[package]] name = "async-timeout" version = "5.0.1" @@ -156,44 +144,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] -[[package]] -name = "black" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419, upload-time = "2025-01-29T05:37:06.642Z" }, - { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080, upload-time = "2025-01-29T05:37:09.321Z" }, - { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886, upload-time = "2025-01-29T04:18:24.432Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404, upload-time = "2025-01-29T04:19:04.296Z" }, - { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372, upload-time = "2025-01-29T05:37:11.71Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865, upload-time = "2025-01-29T05:37:14.309Z" }, - { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699, upload-time = "2025-01-29T04:18:17.688Z" }, - { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028, upload-time = "2025-01-29T04:18:51.711Z" }, - { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988, upload-time = "2025-01-29T05:37:16.707Z" }, - { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985, upload-time = "2025-01-29T05:37:18.273Z" }, - { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816, upload-time = "2025-01-29T04:18:33.823Z" }, - { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860, upload-time = "2025-01-29T04:19:12.944Z" }, - { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, - { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, - { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b6/ae7507470a4830dbbfe875c701e84a4a5fb9183d1497834871a715716a92/black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", size = 1628593, upload-time = "2025-01-29T05:37:23.672Z" }, - { url = "https://files.pythonhosted.org/packages/24/c1/ae36fa59a59f9363017ed397750a0cd79a470490860bc7713967d89cdd31/black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f", size = 1460000, upload-time = "2025-01-29T05:37:25.829Z" }, - { url = "https://files.pythonhosted.org/packages/ac/b6/98f832e7a6c49aa3a464760c67c7856363aa644f2f3c74cf7d624168607e/black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", size = 1765963, upload-time = "2025-01-29T04:18:38.116Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e9/2cb0a017eb7024f70e0d2e9bdb8c5a5b078c5740c7f8816065d06f04c557/black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", size = 1419419, upload-time = "2025-01-29T04:18:30.191Z" }, - { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, -] - [[package]] name = "certifi" version = "2025.1.31" @@ -277,18 +227,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, ] -[[package]] -name = "click" -version = "8.1.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -298,15 +236,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "dill" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, -] - [[package]] name = "dnspython" version = "2.7.0" @@ -338,20 +267,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload-time = "2024-07-12T22:25:58.476Z" }, ] -[[package]] -name = "flake8" -version = "7.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mccabe" }, - { name = "pycodestyle" }, - { name = "pyflakes" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177, upload-time = "2025-03-29T20:08:39.329Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786, upload-time = "2025-03-29T20:08:37.902Z" }, -] - [[package]] name = "frozenlist" version = "1.6.0" @@ -495,15 +410,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] -[[package]] -name = "isort" -version = "6.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955, upload-time = "2025-02-26T21:13:16.955Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186, upload-time = "2025-02-26T21:13:14.911Z" }, -] - [[package]] name = "markupsafe" version = "3.0.2" @@ -668,15 +574,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/75/ab/6677aed7f7b1132ff1fbc2a499d75d1c7243b34839973df61384ac550991/maxminddb-2.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:15b178a5e1af71d7ea8e5374b3ae92d4ccbe52998dc57a737793d6dd029ec97c", size = 36647, upload-time = "2025-05-05T19:31:42.275Z" }, ] -[[package]] -name = "mccabe" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, -] - [[package]] name = "minfraud" version = "3.0.0" @@ -694,14 +591,11 @@ dependencies = [ dev = [ { name = "pytest" }, { name = "pytest-httpserver" }, - { name = "types-requests" }, ] lint = [ - { name = "black" }, - { name = "flake8" }, { name = "mypy" }, - { name = "pylint" }, { name = "ruff" }, + { name = "types-requests" }, ] [package.metadata] @@ -718,14 +612,11 @@ requires-dist = [ dev = [ { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-httpserver", specifier = ">=1.0.10" }, - { name = "types-requests", specifier = ">=2.32.0.20250328" }, ] lint = [ - { name = "black", specifier = ">=25.1.0" }, - { name = "flake8", specifier = ">=7.2.0" }, { name = "mypy", specifier = ">=1.15.0" }, - { name = "pylint", specifier = ">=3.3.6" }, { name = "ruff", specifier = ">=0.11.6" }, + { name = "types-requests", specifier = ">=2.32.0.20250328" }, ] [[package]] @@ -904,24 +795,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.3.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload-time = "2025-03-19T20:36:10.989Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload-time = "2025-03-19T20:36:09.038Z" }, -] - [[package]] name = "pluggy" version = "1.5.0" @@ -1036,44 +909,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/d3/c3cb8f1d6ae3b37f83e1de806713a9b3642c5895f0215a62e1a4bd6e5e34/propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40", size = 12376, upload-time = "2025-03-26T03:06:10.5Z" }, ] -[[package]] -name = "pycodestyle" -version = "2.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312, upload-time = "2025-03-29T17:33:30.669Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424, upload-time = "2025-03-29T17:33:29.405Z" }, -] - -[[package]] -name = "pyflakes" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175, upload-time = "2025-03-31T13:21:20.34Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164, upload-time = "2025-03-31T13:21:18.503Z" }, -] - -[[package]] -name = "pylint" -version = "3.3.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "astroid" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "dill" }, - { name = "isort" }, - { name = "mccabe" }, - { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "tomlkit" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/a7/113d02340afb9dcbb0c8b25454e9538cd08f0ebf3e510df4ed916caa1a89/pylint-3.3.6.tar.gz", hash = "sha256:b634a041aac33706d56a0d217e6587228c66427e20ec21a019bc4cdee48c040a", size = 1519586, upload-time = "2025-03-20T11:25:38.207Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/21/9537fc94aee9ec7316a230a49895266cf02d78aa29b0a2efbc39566e0935/pylint-3.3.6-py3-none-any.whl", hash = "sha256:8b7c2d3e86ae3f94fb27703d521dd0b9b6b378775991f504d7c3a6275aa0a6a6", size = 522462, upload-time = "2025-03-20T11:25:36.13Z" }, -] - [[package]] name = "pytest" version = "8.3.5" @@ -1182,15 +1017,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] -[[package]] -name = "tomlkit" -version = "0.13.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885, upload-time = "2024-08-14T08:19:41.488Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955, upload-time = "2024-08-14T08:19:40.05Z" }, -] - [[package]] name = "types-requests" version = "2.32.0.20250328"