From 82f2af60dfc9eec42323bdf166ccc758f289a560 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 7 Jan 2020 14:15:03 -0800 Subject: [PATCH 1/3] Removing Python 2 support --- .travis.yml | 2 +- CONTRIBUTING.md | 49 +------------------------------------- README.md | 6 ++--- requirements.txt | 1 - scripts/prepare_release.sh | 2 +- setup.py | 8 +++---- tox.ini | 33 ------------------------- 7 files changed, 8 insertions(+), 93 deletions(-) delete mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml index 4db3c3708..0c00ccc23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: python python: - - "2.7" - "3.4" - "3.5" - "3.6" + - "3.7" - "pypy3.5" jobs: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b4a0ea84..7b521ec99 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,7 +85,7 @@ information on using pull requests. ### Initial Setup -You need Python 2.7 or Python 3.4+ to build and test the code in this repo. +You need Python 3.4+ to build and test the code in this repo. We recommend using [pip](https://pypi.python.org/pypi/pip) for installing the necessary tools and project dependencies. Most recent versions of Python ship with pip. If your development environment @@ -227,53 +227,6 @@ pytest --cov --cov-report html and point your browser to `file:////htmlcov/index.html` (where `dir` is the location from which the report was created). - -### Testing in Different Environments - -Sometimes we want to run unit tests in multiple environments (e.g. different Python versions), and -ensure that the SDK works as expected in each of them. We use -[tox](https://tox.readthedocs.io/en/latest/) for this purpose. - -But before you can invoke tox, you must set up all the necessary target environments on your -workstation. The easiest and cleanest way to achieve this is by using a tool like -[pyenv](https://github.com/pyenv/pyenv). Refer to the -[pyenv documentation](https://github.com/pyenv/pyenv#installation) for instructions on how to -install it. This generally involves installing some binaries as well as modifying a system level -configuration file such as `.bash_profile`. Once pyenv is installed, you can install multiple -versions of Python as follows: - -``` -pyenv install 2.7.6 # install Python 2.7.6 -pyenv install 3.3.0 # install Python 3.3.0 -pyenv install pypy2-5.6.0 # install pypy2 -``` - -Refer to the [`tox.ini`](tox.ini) file for a list of target environments that we usually test. -Use pyenv to install all the required Python versions on your workstation. Verify that they are -installed by running the following command: - -``` -pyenv versions -``` - -To make all the required Python versions available to tox for testing, run the `pyenv local` command -with all the Python versions as arguments. The following example shows how to make Python versions -2.7.6, 3.3.0 and pypy2 available to tox. - -``` -pyenv local 2.7.6 3.3.0 pypy2-5.6.0 -``` - -Once your system is fully set up, you can execute the following command from the root of the -repository to launch tox: - -``` -tox -``` - -This command will read the list of target environments from `tox.ini`, and execute tests in each of -those environments. It will also generate a code coverage report at the end of the execution. - ### Repo Organization Here are some highlights of the directory structure and notable source files diff --git a/README.md b/README.md index 757a3f8cd..8e9efd0ee 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,8 @@ requests, code review feedback, and also pull requests. ## Supported Python Versions -We currently support Python 2.7 and Python 3.4+. However, Python 2.7 support is -being phased out, and the developers are advised to use latest Python 3. -Firebase Admin Python SDK is also tested on PyPy and -[Google App Engine](https://cloud.google.com/appengine/) environments. +We currently support Python 3.4+. Firebase Admin Python SDK is also tested on +PyPy and [Google App Engine](https://cloud.google.com/appengine/) environments. ## Documentation diff --git a/requirements.txt b/requirements.txt index cc4534b03..2f1a09a5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ pylint == 1.6.4 pytest >= 3.6.0 pytest-cov >= 2.4.0 pytest-localserver >= 0.4.1 -tox >= 3.6.0 cachecontrol >= 0.12.6 google-api-core[grpc] >= 1.14.0, < 2.0.0dev; platform.python_implementation != 'PyPy' diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index ca30d9043..aa55dae92 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -132,7 +132,7 @@ fi ################## echo "[INFO] Running unit tests" -tox +pytest ../tests echo "[INFO] Running integration tests" pytest ../integration --cert cert.json --apikey apikey.txt diff --git a/setup.py b/setup.py index cb698f774..b492ec922 100644 --- a/setup.py +++ b/setup.py @@ -22,8 +22,8 @@ (major, minor) = (sys.version_info.major, sys.version_info.minor) -if (major == 2 and minor < 7) or (major == 3 and minor < 4): - print('firebase_admin requires python2 >= 2.7 or python3 >= 3.4', file=sys.stderr) +if major != 3 or minor < 4: + print('firebase_admin requires python >= 3.4', file=sys.stderr) sys.exit(1) # Read in the package metadata per recommendations from: @@ -56,13 +56,11 @@ keywords='firebase cloud development', install_requires=install_requires, packages=['firebase_admin'], - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=3.4', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Topic :: Software Development :: Build Tools', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', diff --git a/tox.ini b/tox.ini deleted file mode 100644 index dec7b618f..000000000 --- a/tox.ini +++ /dev/null @@ -1,33 +0,0 @@ -# Tox (https://tox.readthedocs.io/) is a tool for running tests -# in multiple virtualenvs. This configuration file will run the -# test suite on all supported python versions. To use it, "pip install tox" -# and then run "tox" from this directory. - -[tox] -envlist = py2,py3,pypy,cover - -[testenv] -passenv = - FIREBASE_DATABASE_EMULATOR_HOST -commands = pytest {posargs} -deps = - pytest - pytest-localserver - -[coverbase] -basepython = python2.7 -commands = - pytest \ - --cov=firebase_admin \ - --cov=tests -deps = {[testenv]deps} - coverage - pytest-cov - -[testenv:cover] -basepython = {[coverbase]basepython} -commands = - {[coverbase]commands} - coverage report --show-missing -deps = - {[coverbase]deps} From 3419f58072c1e0c647aa66564d77d7e670251ee9 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 7 Jan 2020 15:54:19 -0800 Subject: [PATCH 2/3] Upgraded to Pylint 2.x and fixed all linter errors for Python 3 --- .travis.yml | 2 +- firebase_admin/__init__.py | 36 +++++++------- firebase_admin/_auth_utils.py | 4 +- firebase_admin/_http_client.py | 2 +- firebase_admin/_messaging_encoder.py | 34 ++++++------- firebase_admin/_messaging_utils.py | 32 ++++++------- firebase_admin/_sseclient.py | 10 ++-- firebase_admin/_token_gen.py | 10 ++-- firebase_admin/_user_import.py | 10 ++-- firebase_admin/_user_mgt.py | 20 ++++---- firebase_admin/_utils.py | 18 +++---- firebase_admin/auth.py | 9 ++-- firebase_admin/credentials.py | 2 +- firebase_admin/db.py | 64 ++++++++++++------------- firebase_admin/firestore.py | 2 +- firebase_admin/instance_id.py | 6 +-- firebase_admin/messaging.py | 11 +++-- firebase_admin/project_management.py | 18 +++---- firebase_admin/storage.py | 4 +- integration/test_auth.py | 8 ++-- integration/test_db.py | 14 +++--- integration/test_messaging.py | 2 +- lint.sh | 2 +- requirements.txt | 2 +- snippets/auth/index.py | 4 +- tests/test_app.py | 10 ++-- tests/test_credentials.py | 8 ++-- tests/test_db.py | 28 +++++------ tests/test_exceptions.py | 6 +-- tests/test_firestore.py | 2 +- tests/test_http_client.py | 2 +- tests/test_instance_id.py | 4 +- tests/test_messaging.py | 72 ++++++++++++++-------------- tests/test_project_management.py | 11 ++--- tests/test_sseclient.py | 6 +-- tests/test_token_gen.py | 10 ++-- tests/test_user_mgt.py | 51 ++++++++++---------- tests/testutils.py | 6 +-- 38 files changed, 271 insertions(+), 271 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0c00ccc23..8d6b9246a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: jobs: include: - name: "Lint" - python: "2.7" + python: "3.7" script: ./lint.sh all before_install: diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index bc9526378..eae68bd06 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -77,12 +77,12 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME): 'initialize_app() once. But if you do want to initialize multiple ' 'apps, pass a second argument to initialize_app() to give each app ' 'a unique name.')) - else: - raise ValueError(( - 'Firebase app named "{0}" already exists. This means you called ' - 'initialize_app() more than once with the same app name as the ' - 'second argument. Make sure you provide a unique name every time ' - 'you call initialize_app().').format(name)) + + raise ValueError(( + 'Firebase app named "{0}" already exists. This means you called ' + 'initialize_app() more than once with the same app name as the ' + 'second argument. Make sure you provide a unique name every time ' + 'you call initialize_app().').format(name)) def delete_app(app): @@ -106,11 +106,11 @@ def delete_app(app): raise ValueError( 'The default Firebase app is not initialized. Make sure to initialize ' 'the default app by calling initialize_app().') - else: - raise ValueError( - ('Firebase app named "{0}" is not initialized. Make sure to initialize ' - 'the app by calling initialize_app() with your app name as the ' - 'second argument.').format(app.name)) + + raise ValueError( + ('Firebase app named "{0}" is not initialized. Make sure to initialize ' + 'the app by calling initialize_app() with your app name as the ' + 'second argument.').format(app.name)) def get_app(name=_DEFAULT_APP_NAME): @@ -137,14 +137,14 @@ def get_app(name=_DEFAULT_APP_NAME): raise ValueError( 'The default Firebase app does not exist. Make sure to initialize ' 'the SDK by calling initialize_app().') - else: - raise ValueError( - ('Firebase app named "{0}" does not exist. Make sure to initialize ' - 'the SDK by calling initialize_app() with your app name as the ' - 'second argument.').format(name)) + + raise ValueError( + ('Firebase app named "{0}" does not exist. Make sure to initialize ' + 'the SDK by calling initialize_app() with your app name as the ' + 'second argument.').format(name)) -class _AppOptions(object): +class _AppOptions: """A collection of configuration options for an App.""" def __init__(self, options): @@ -185,7 +185,7 @@ def _load_from_environment(self): return {k: v for k, v in json_data.items() if k in _CONFIG_VALID_KEYS} -class App(object): +class App: """The entry point for Firebase Python SDK. Represents a Firebase app, while holding the configuration and state diff --git a/firebase_admin/_auth_utils.py b/firebase_admin/_auth_utils.py index df3e0acfc..b54e7d480 100644 --- a/firebase_admin/_auth_utils.py +++ b/firebase_admin/_auth_utils.py @@ -103,6 +103,7 @@ def validate_provider_id(provider_id, required=True): return provider_id def validate_photo_url(photo_url, required=False): + """Parses and validates the given URL string.""" if photo_url is None and not required: return None if not isinstance(photo_url, six.string_types) or not photo_url: @@ -118,6 +119,7 @@ def validate_photo_url(photo_url, required=False): raise ValueError('Malformed photo URL: "{0}".'.format(photo_url)) def validate_timestamp(timestamp, label, required=False): + """Validates the given timestamp value. Timestamps must be positive integers.""" if timestamp is None and not required: return None if isinstance(timestamp, bool): @@ -181,7 +183,7 @@ def validate_custom_claims(custom_claims, required=False): if len(invalid_claims) > 1: joined = ', '.join(sorted(invalid_claims)) raise ValueError('Claims "{0}" are reserved, and must not be set.'.format(joined)) - elif len(invalid_claims) == 1: + if len(invalid_claims) == 1: raise ValueError( 'Claim "{0}" is reserved, and must not be set.'.format(invalid_claims.pop())) return claims_str diff --git a/firebase_admin/_http_client.py b/firebase_admin/_http_client.py index eb8c4027a..1daaf371b 100644 --- a/firebase_admin/_http_client.py +++ b/firebase_admin/_http_client.py @@ -32,7 +32,7 @@ raise_on_status=False, backoff_factor=0.5) -class HttpClient(object): +class HttpClient: """Base HTTP client used to make HTTP calls. HttpClient maintains an HTTP session, and handles request authentication and retries if diff --git a/firebase_admin/_messaging_encoder.py b/firebase_admin/_messaging_encoder.py index a65b2f4ee..aefaf3e2f 100644 --- a/firebase_admin/_messaging_encoder.py +++ b/firebase_admin/_messaging_encoder.py @@ -25,7 +25,7 @@ import firebase_admin._messaging_utils as _messaging_utils -class Message(object): +class Message: """A message that can be sent via Firebase Cloud Messaging. Contains payload information as well as recipient information. In particular, the message must @@ -61,7 +61,7 @@ def __str__(self): return json.dumps(self, cls=MessageEncoder, sort_keys=True) -class MulticastMessage(object): +class MulticastMessage: """A message that can be sent to multiple tokens via Firebase Cloud Messaging. Args: @@ -88,7 +88,7 @@ def __init__(self, tokens, data=None, notification=None, android=None, webpush=N self.fcm_options = fcm_options -class _Validators(object): +class _Validators: """A collection of data validation utilities. Methods provided in this class raise ``ValueErrors`` if any validations fail. @@ -102,8 +102,7 @@ def check_string(cls, label, value, non_empty=False): if not isinstance(value, six.string_types): if non_empty: raise ValueError('{0} must be a non-empty string.'.format(label)) - else: - raise ValueError('{0} must be a string.'.format(label)) + raise ValueError('{0} must be a string.'.format(label)) if non_empty and not value: raise ValueError('{0} must be a non-empty string.'.format(label)) return value @@ -647,6 +646,7 @@ def encode_notification(cls, notification): @classmethod def sanitize_topic_name(cls, topic): + """Removes the /topics/ prefix from the topic name, if present.""" if not topic: return None prefix = '/topics/' @@ -657,20 +657,20 @@ def sanitize_topic_name(cls, topic): raise ValueError('Malformed topic name.') return topic - def default(self, obj): # pylint: disable=method-hidden - if not isinstance(obj, Message): - return json.JSONEncoder.default(self, obj) + def default(self, o): # pylint: disable=method-hidden + if not isinstance(o, Message): + return json.JSONEncoder.default(self, o) result = { - 'android': MessageEncoder.encode_android(obj.android), - 'apns': MessageEncoder.encode_apns(obj.apns), + 'android': MessageEncoder.encode_android(o.android), + 'apns': MessageEncoder.encode_apns(o.apns), 'condition': _Validators.check_string( - 'Message.condition', obj.condition, non_empty=True), - 'data': _Validators.check_string_dict('Message.data', obj.data), - 'notification': MessageEncoder.encode_notification(obj.notification), - 'token': _Validators.check_string('Message.token', obj.token, non_empty=True), - 'topic': _Validators.check_string('Message.topic', obj.topic, non_empty=True), - 'webpush': MessageEncoder.encode_webpush(obj.webpush), - 'fcm_options': MessageEncoder.encode_fcm_options(obj.fcm_options), + 'Message.condition', o.condition, non_empty=True), + 'data': _Validators.check_string_dict('Message.data', o.data), + 'notification': MessageEncoder.encode_notification(o.notification), + 'token': _Validators.check_string('Message.token', o.token, non_empty=True), + 'topic': _Validators.check_string('Message.topic', o.topic, non_empty=True), + 'webpush': MessageEncoder.encode_webpush(o.webpush), + 'fcm_options': MessageEncoder.encode_fcm_options(o.fcm_options), } result['topic'] = MessageEncoder.sanitize_topic_name(result.get('topic')) result = MessageEncoder.remove_null_values(result) diff --git a/firebase_admin/_messaging_utils.py b/firebase_admin/_messaging_utils.py index 10ede8a5b..3a1943c04 100644 --- a/firebase_admin/_messaging_utils.py +++ b/firebase_admin/_messaging_utils.py @@ -17,7 +17,7 @@ from firebase_admin import exceptions -class Notification(object): +class Notification: """A notification that can be included in a message. Args: @@ -32,7 +32,7 @@ def __init__(self, title=None, body=None, image=None): self.image = image -class AndroidConfig(object): +class AndroidConfig: """Android-specific options that can be included in a message. Args: @@ -62,7 +62,7 @@ def __init__(self, collapse_key=None, priority=None, ttl=None, restricted_packag self.fcm_options = fcm_options -class AndroidNotification(object): +class AndroidNotification: """Android-specific notification parameters. Args: @@ -178,7 +178,7 @@ def __init__(self, title=None, body=None, icon=None, color=None, sound=None, tag self.notification_count = notification_count -class LightSettings(object): +class LightSettings: """Represents settings to control notification LED that can be included in a ``messaging.AndroidNotification``. @@ -196,7 +196,7 @@ def __init__(self, color, light_on_duration_millis, self.light_off_duration_millis = light_off_duration_millis -class AndroidFCMOptions(object): +class AndroidFCMOptions: """Options for features provided by the FCM SDK for Android. Args: @@ -208,7 +208,7 @@ def __init__(self, analytics_label=None): self.analytics_label = analytics_label -class WebpushConfig(object): +class WebpushConfig: """Webpush-specific options that can be included in a message. Args: @@ -230,7 +230,7 @@ def __init__(self, headers=None, data=None, notification=None, fcm_options=None) self.fcm_options = fcm_options -class WebpushNotificationAction(object): +class WebpushNotificationAction: """An action available to the users when the notification is presented. Args: @@ -245,7 +245,7 @@ def __init__(self, action, title, icon=None): self.icon = icon -class WebpushNotification(object): +class WebpushNotification: """Webpush-specific notification parameters. Refer to the `Notification Reference`_ for more information. @@ -302,7 +302,7 @@ def __init__(self, title=None, body=None, icon=None, actions=None, badge=None, d self.custom_data = custom_data -class WebpushFCMOptions(object): +class WebpushFCMOptions: """Options for features provided by the FCM SDK for Web. Args: @@ -314,7 +314,7 @@ def __init__(self, link=None): self.link = link -class APNSConfig(object): +class APNSConfig: """APNS-specific options that can be included in a message. Refer to `APNS Documentation`_ for more information. @@ -335,7 +335,7 @@ def __init__(self, headers=None, payload=None, fcm_options=None): self.fcm_options = fcm_options -class APNSPayload(object): +class APNSPayload: """Payload of an APNS message. Args: @@ -349,7 +349,7 @@ def __init__(self, aps, **kwargs): self.custom_data = kwargs -class Aps(object): +class Aps: """Aps dictionary to be included in an APNS payload. Args: @@ -379,7 +379,7 @@ def __init__(self, alert=None, badge=None, sound=None, content_available=None, c self.custom_data = custom_data -class CriticalSound(object): +class CriticalSound: """Critical alert sound configuration that can be included in ``messaging.Aps``. Args: @@ -398,7 +398,7 @@ def __init__(self, name, critical=None, volume=None): self.volume = volume -class ApsAlert(object): +class ApsAlert: """An alert that can be included in ``messaging.Aps``. Args: @@ -437,7 +437,7 @@ def __init__(self, title=None, subtitle=None, body=None, loc_key=None, loc_args= self.custom_data = custom_data -class APNSFCMOptions(object): +class APNSFCMOptions: """Options for features provided by the FCM SDK for iOS. Args: @@ -452,7 +452,7 @@ def __init__(self, analytics_label=None, image=None): self.image = image -class FCMOptions(object): +class FCMOptions: """Options for features provided by SDK. Args: diff --git a/firebase_admin/_sseclient.py b/firebase_admin/_sseclient.py index eab79f9e3..6585dfc80 100644 --- a/firebase_admin/_sseclient.py +++ b/firebase_admin/_sseclient.py @@ -40,7 +40,7 @@ def rebuild_auth(self, prepared_request, response): pass -class _EventBuffer(object): +class _EventBuffer: """A helper class for buffering and parsing raw SSE data.""" def __init__(self): @@ -68,7 +68,7 @@ def buffer_string(self): return ''.join(self._buffer) -class SSEClient(object): +class SSEClient: """SSE client implementation.""" def __init__(self, url, session, retry=3000, **kwargs): @@ -140,7 +140,7 @@ def __next__(self): if event.data == 'credential is no longer valid': self._connect() return None - elif event.data == 'null': + if event.data == 'null': return None # If the server requests a specific retry delay, we need to honor it. @@ -157,7 +157,7 @@ def next(self): return self.__next__() -class Event(object): +class Event: """Event represents the events fired by SSE.""" sse_line_pattern = re.compile('(?P[^:]*):?( ?(?P.*))?') @@ -192,7 +192,7 @@ def parse(cls, raw): if name == '': # line began with a ":", so is a comment. Ignore continue - elif name == 'data': + if name == 'data': # If we already have some data, then join to it with a newline. # Else this is it. if event.data: diff --git a/firebase_admin/_token_gen.py b/firebase_admin/_token_gen.py index 339714dcd..471630cca 100644 --- a/firebase_admin/_token_gen.py +++ b/firebase_admin/_token_gen.py @@ -55,7 +55,7 @@ 'service-accounts/default/email') -class _SigningProvider(object): +class _SigningProvider: """Stores a reference to a google.auth.crypto.Signer.""" def __init__(self, signer, signer_email): @@ -80,7 +80,7 @@ def from_iam(cls, request, google_cred, service_account): return _SigningProvider(signer, service_account) -class TokenGenerator(object): +class TokenGenerator: """Generates custom tokens and session cookies.""" def __init__(self, app, client): @@ -207,7 +207,7 @@ def create_session_cookie(self, id_token, expires_in): return body.get('sessionCookie') -class TokenVerifier(object): +class TokenVerifier: """Verifies ID tokens and session cookies.""" def __init__(self, app): @@ -237,7 +237,7 @@ def verify_session_cookie(self, cookie): return self.cookie_verifier.verify(cookie, self.request) -class _JWTVerifier(object): +class _JWTVerifier: """Verifies Firebase JWTs (ID tokens or session cookies).""" def __init__(self, **kwargs): @@ -288,7 +288,7 @@ def verify(self, token, request): 'token.'.format(self.operation, self.articled_short_name)) elif not header.get('kid'): if header.get('alg') == 'HS256' and payload.get( - 'v') is 0 and 'uid' in payload.get('d', {}): + 'v') == 0 and 'uid' in payload.get('d', {}): error_message = ( '{0} expects {1}, but was given a legacy custom ' 'token.'.format(self.operation, self.articled_short_name)) diff --git a/firebase_admin/_user_import.py b/firebase_admin/_user_import.py index 86252ffb8..21cc8082d 100644 --- a/firebase_admin/_user_import.py +++ b/firebase_admin/_user_import.py @@ -24,7 +24,7 @@ def b64_encode(bytes_value): return base64.urlsafe_b64encode(bytes_value).decode() -class UserProvider(object): +class UserProvider: """Represents a user identity provider that can be associated with a Firebase user. One or more providers can be specified in an ``ImportUserRecord`` when importing users via @@ -97,7 +97,7 @@ def to_dict(self): return {k: v for k, v in payload.items() if v is not None} -class ImportUserRecord(object): +class ImportUserRecord: """Represents a user account to be imported to Firebase Auth. Must specify the ``uid`` field at a minimum. A sequence of ``ImportUserRecord`` objects can be @@ -255,7 +255,7 @@ def to_dict(self): return {k: v for k, v in payload.items() if v is not None} -class UserImportHash(object): +class UserImportHash: """Represents a hash algorithm used to hash user passwords. An instance of this class must be specified when importing users with passwords via the @@ -471,7 +471,7 @@ def standard_scrypt(cls, memory_cost, parallelization, block_size, derived_key_l return UserImportHash('STANDARD_SCRYPT', data) -class ErrorInfo(object): +class ErrorInfo: """Represents an error encountered while importing an ``ImportUserRecord``.""" def __init__(self, error): @@ -487,7 +487,7 @@ def reason(self): return self._reason -class UserImportResult(object): +class UserImportResult: """Represents the result of a bulk user import operation. See ``auth.import_users()`` API for more details. diff --git a/firebase_admin/_user_mgt.py b/firebase_admin/_user_mgt.py index 2e10fac1b..5b33abb39 100644 --- a/firebase_admin/_user_mgt.py +++ b/firebase_admin/_user_mgt.py @@ -29,7 +29,7 @@ B64_REDACTED = base64.b64encode(b'REDACTED') -class Sentinel(object): +class Sentinel: def __init__(self, description): self.description = description @@ -38,7 +38,7 @@ def __init__(self, description): DELETE_ATTRIBUTE = Sentinel('Value used to delete an attribute from a user profile') -class UserMetadata(object): +class UserMetadata: """Contains additional metadata associated with a user account.""" def __init__(self, creation_timestamp=None, last_sign_in_timestamp=None): @@ -66,7 +66,7 @@ def last_sign_in_timestamp(self): return self._last_sign_in_timestamp -class UserInfo(object): +class UserInfo: """A collection of standard profile information for a user. Used to expose profile information returned by an identity provider. @@ -248,9 +248,6 @@ def custom_claims(self): class ExportedUserRecord(UserRecord): """Contains metadata associated with a user including password hash and salt.""" - def __init__(self, data): - super(ExportedUserRecord, self).__init__(data) - @property def password_hash(self): """The user's password hash as a base64-encoded string. @@ -283,7 +280,7 @@ def password_salt(self): return self._data.get('salt') -class ListUsersPage(object): +class ListUsersPage: """Represents a page of user records exported from a Firebase project. Provides methods for traversing the user accounts included in this page, as well as retrieving @@ -370,7 +367,7 @@ def provider_id(self): return self._data.get('providerId') -class ActionCodeSettings(object): +class ActionCodeSettings: """Contains required continue/state URL with optional Android and iOS settings. Used when invoking the email action link generation APIs. """ @@ -454,7 +451,7 @@ def encode_action_code_settings(settings): return parameters -class UserManager(object): +class UserManager: """Provides methods for interacting with the Google Identity Toolkit.""" def __init__(self, client): @@ -493,7 +490,7 @@ def list_users(self, page_token=None, max_results=MAX_LIST_USERS_RESULTS): raise ValueError('Page token must be a non-empty string.') if not isinstance(max_results, int): raise ValueError('Max results must be an integer.') - elif max_results < 1 or max_results > MAX_LIST_USERS_RESULTS: + if max_results < 1 or max_results > MAX_LIST_USERS_RESULTS: raise ValueError( 'Max results must be a positive integer less than ' '{0}.'.format(MAX_LIST_USERS_RESULTS)) @@ -636,6 +633,7 @@ def generate_email_action_link(self, action_type, email, action_code_settings=No link_url: action url to be emailed to the user Raises: + UnexpectedResponseError: If the backend server responds with an unexpected message FirebaseError: If an error occurs while generating the link ValueError: If the provided arguments are invalid """ @@ -660,7 +658,7 @@ def generate_email_action_link(self, action_type, email, action_code_settings=No return body.get('oobLink') -class _UserIterator(object): +class _UserIterator: """An iterator that allows iterating over user accounts, one at a time. This implementation loads a page of users into memory, and iterates on them. When the whole diff --git a/firebase_admin/_utils.py b/firebase_admin/_utils.py index 95ed2c414..7ec1b8fb8 100644 --- a/firebase_admin/_utils.py +++ b/firebase_admin/_utils.py @@ -60,17 +60,19 @@ def _get_initialized_app(app): + """Returns a reference to an initialized App instance.""" if app is None: return firebase_admin.get_app() - elif isinstance(app, firebase_admin.App): + + if isinstance(app, firebase_admin.App): initialized_app = firebase_admin.get_app(app.name) if app is not initialized_app: raise ValueError('Illegal app argument. App instance not ' 'initialized via the firebase module.') return app - else: - raise ValueError('Illegal app argument. Argument must be of type ' - ' firebase_admin.App, but given "{0}".'.format(type(app))) + + raise ValueError('Illegal app argument. Argument must be of type ' + ' firebase_admin.App, but given "{0}".'.format(type(app))) def get_app_service(app, name, initializer): @@ -143,11 +145,11 @@ def handle_requests_error(error, message=None, code=None): return exceptions.DeadlineExceededError( message='Timed out while making an API call: {0}'.format(error), cause=error) - elif isinstance(error, requests.exceptions.ConnectionError): + if isinstance(error, requests.exceptions.ConnectionError): return exceptions.UnavailableError( message='Failed to establish a connection: {0}'.format(error), cause=error) - elif error.response is None: + if error.response is None: return exceptions.UnknownError( message='Unknown error while making a remote service call: {0}'.format(error), cause=error) @@ -230,11 +232,11 @@ def handle_googleapiclient_error(error, message=None, code=None, http_response=N return exceptions.DeadlineExceededError( message='Timed out while making an API call: {0}'.format(error), cause=error) - elif isinstance(error, httplib2.ServerNotFoundError): + if isinstance(error, httplib2.ServerNotFoundError): return exceptions.UnavailableError( message='Failed to establish a connection: {0}'.format(error), cause=error) - elif not isinstance(error, googleapiclient.errors.HttpError): + if not isinstance(error, googleapiclient.errors.HttpError): return exceptions.UnknownError( message='Unknown error while making a remote service call: {0}'.format(error), cause=error) diff --git a/firebase_admin/auth.py b/firebase_admin/auth.py index a5110c211..6f85e622c 100644 --- a/firebase_admin/auth.py +++ b/firebase_admin/auth.py @@ -337,9 +337,12 @@ def download(page_token, max_results): return ListUsersPage(download, page_token, max_results) -def create_user(**kwargs): +def create_user(**kwargs): # pylint: disable=differing-param-doc """Creates a new user account with the specified properties. + Args: + kwargs: A series of keyword arguments (optional). + Keyword Args: uid: User ID to assign to the newly created user (optional). display_name: The user's display name (optional). @@ -365,7 +368,7 @@ def create_user(**kwargs): return UserRecord(user_manager.get_user(uid=uid)) -def update_user(uid, **kwargs): +def update_user(uid, **kwargs): # pylint: disable=differing-param-doc """Updates an existing user account with the specified properties. Args: @@ -542,7 +545,7 @@ def _check_jwt_revoked(verified_claims, exc_type, label, app): raise exc_type('The Firebase {0} has been revoked.'.format(label)) -class _AuthService(object): +class _AuthService: """Firebase Authentication service.""" ID_TOOLKIT_URL = 'https://identitytoolkit.googleapis.com/v1/projects/' diff --git a/firebase_admin/credentials.py b/firebase_admin/credentials.py index 2e400d9e4..e930675bd 100644 --- a/firebase_admin/credentials.py +++ b/firebase_admin/credentials.py @@ -41,7 +41,7 @@ """ -class Base(object): +class Base: """Provides OAuth2 access tokens for accessing Firebase services.""" def get_access_token(self): diff --git a/firebase_admin/db.py b/firebase_admin/db.py index ef7c96721..2fb8b3a74 100644 --- a/firebase_admin/db.py +++ b/firebase_admin/db.py @@ -81,7 +81,7 @@ def _parse_path(path): return [seg for seg in path.split('/') if seg] -class Event(object): +class Event: """Represents a realtime update event received from the database.""" def __init__(self, sse_event): @@ -104,7 +104,7 @@ def event_type(self): return self._sse_event.event_type -class ListenerRegistration(object): +class ListenerRegistration: """Represents the addition of an event listener to a database reference.""" def __init__(self, callback, sse): @@ -138,7 +138,7 @@ def close(self): self._thread.join() -class Reference(object): +class Reference: """Reference represents a node in the Firebase realtime database.""" def __init__(self, **kwargs): @@ -218,9 +218,9 @@ def get(self, etag=False, shallow=False): headers, data = self._client.headers_and_body( 'get', self._add_suffix(), headers={'X-Firebase-ETag' : 'true'}) return data, headers.get('ETag') - else: - params = 'shallow=true' if shallow else None - return self._client.body('get', self._add_suffix(), params=params) + + params = 'shallow=true' if shallow else None + return self._client.body('get', self._add_suffix(), params=params) def get_if_changed(self, etag): """Gets data in this location only if the specified ETag does not match. @@ -245,8 +245,8 @@ def get_if_changed(self, etag): resp = self._client.request('get', self._add_suffix(), headers={'if-none-match': etag}) if resp.status_code == 304: return False, None, None - else: - return True, resp.json(), resp.headers.get('ETag') + + return True, resp.json(), resp.headers.get('ETag') def set(self, value): """Sets the data at this location to the given value. @@ -300,8 +300,8 @@ def set_if_unchanged(self, expected_etag, value): etag = http_response.headers['ETag'] snapshot = http_response.json() return False, snapshot, etag - else: - raise error + + raise error def push(self, value=''): """Creates a new child node. @@ -473,7 +473,7 @@ def _listen_with_session(self, callback, session): raise _Client.handle_rtdb_error(error) -class Query(object): +class Query: """Represents a complex query that can be executed on a Reference. Complex queries can consist of up to 2 components: a required ordering constraint, and an @@ -631,7 +631,7 @@ def __init__(self, message): exceptions.AbortedError.__init__(self, message) -class _Sorter(object): +class _Sorter: """Helper class for sorting query results.""" def __init__(self, results, order_by): @@ -648,11 +648,11 @@ def __init__(self, results, order_by): def get(self): if self.dict_input: return collections.OrderedDict([(e.key, e.value) for e in self.sort_entries]) - else: - return [e.value for e in self.sort_entries] + return [e.value for e in self.sort_entries] -class _SortEntry(object): + +class _SortEntry: """A wrapper that is capable of sorting items in a dictionary.""" _type_none = 0 @@ -665,7 +665,7 @@ class _SortEntry(object): def __init__(self, key, value, order_by): self._key = key self._value = value - if order_by == '$key' or order_by == '$priority': + if order_by in ('$key', '$priority'): self._index = key elif order_by == '$value': self._index = value @@ -698,16 +698,16 @@ def _get_index_type(cls, index): """ if index is None: return cls._type_none - elif isinstance(index, bool) and not index: + if isinstance(index, bool) and not index: return cls._type_bool_false - elif isinstance(index, bool) and index: + if isinstance(index, bool) and index: return cls._type_bool_true - elif isinstance(index, (int, float)): + if isinstance(index, (int, float)): return cls._type_numeric - elif isinstance(index, six.string_types): + if isinstance(index, six.string_types): return cls._type_string - else: - return cls._type_object + + return cls._type_object @classmethod def _extract_child(cls, value, path): @@ -737,10 +737,10 @@ def _compare(self, other): if self_key < other_key: return -1 - elif self_key > other_key: + if self_key > other_key: return 1 - else: - return 0 + + return 0 def __lt__(self, other): return self._compare(other) < 0 @@ -755,10 +755,10 @@ def __ge__(self, other): return self._compare(other) >= 0 def __eq__(self, other): - return self._compare(other) is 0 + return self._compare(other) == 0 -class _DatabaseService(object): +class _DatabaseService: """Service that maintains a collection of database clients.""" _DEFAULT_AUTH_OVERRIDE = '_admin_' @@ -772,7 +772,7 @@ def __init__(self, app): else: self._db_url = None auth_override = _DatabaseService._get_auth_override(app) - if auth_override != self._DEFAULT_AUTH_OVERRIDE and auth_override != {}: + if auth_override not in (self._DEFAULT_AUTH_OVERRIDE, {}): self._auth_override = json.dumps(auth_override, separators=(',', ':')) else: self._auth_override = None @@ -832,8 +832,8 @@ def _parse_db_url(cls, url, emulator_host=None): parsed_url = urllib.parse.urlparse(url) if parsed_url.netloc.endswith('.firebaseio.com'): return cls._parse_production_url(parsed_url, emulator_host) - else: - return cls._parse_emulator_url(parsed_url) + + return cls._parse_emulator_url(parsed_url) @classmethod def _parse_production_url(cls, parsed_url, emulator_host): @@ -875,8 +875,8 @@ def _get_auth_override(cls, app): if not isinstance(auth_override, dict): raise ValueError('Invalid databaseAuthVariableOverride option: "{0}". Override ' 'value must be a dict or None.'.format(auth_override)) - else: - return auth_override + + return auth_override def close(self): for value in self._clients.values(): diff --git a/firebase_admin/firestore.py b/firebase_admin/firestore.py index a9887b195..32c9897d5 100644 --- a/firebase_admin/firestore.py +++ b/firebase_admin/firestore.py @@ -54,7 +54,7 @@ def client(app=None): return fs_client.get() -class _FirestoreClient(object): +class _FirestoreClient: """Holds a Google Cloud Firestore client instance.""" def __init__(self, credentials, project): diff --git a/firebase_admin/instance_id.py b/firebase_admin/instance_id.py index e9134fc28..f90d058cc 100644 --- a/firebase_admin/instance_id.py +++ b/firebase_admin/instance_id.py @@ -53,7 +53,7 @@ def delete_instance_id(instance_id, app=None): _get_iid_service(app).delete_instance_id(instance_id) -class _InstanceIdService(object): +class _InstanceIdService: """Provides methods for interacting with the remote instance ID service.""" error_codes = { @@ -96,5 +96,5 @@ def _extract_message(self, instance_id, error): msg = self.error_codes.get(status) if msg: return 'Instance ID "{0}": {1}'.format(instance_id, msg) - else: - return 'Instance ID "{0}": {1}'.format(instance_id, error) + + return 'Instance ID "{0}": {1}'.format(instance_id, error) diff --git a/firebase_admin/messaging.py b/firebase_admin/messaging.py index a35afc87a..71366e5c4 100644 --- a/firebase_admin/messaging.py +++ b/firebase_admin/messaging.py @@ -206,7 +206,7 @@ def unsubscribe_from_topic(tokens, topic, app=None): tokens, topic, 'iid/v1:batchRemove') -class ErrorInfo(object): +class ErrorInfo: """An error encountered when performing a topic management operation.""" def __init__(self, index, reason): @@ -224,7 +224,7 @@ def reason(self): return self._reason -class TopicManagementResponse(object): +class TopicManagementResponse: """The response received from a topic management operation.""" def __init__(self, resp): @@ -256,7 +256,7 @@ def errors(self): return self._errors -class BatchResponse(object): +class BatchResponse: """The response received from a batch request to the FCM API.""" def __init__(self, responses): @@ -277,7 +277,7 @@ def failure_count(self): return len(self.responses) - self.success_count -class SendResponse(object): +class SendResponse: """The response received from an individual batched request to the FCM API.""" def __init__(self, resp, exception): @@ -302,7 +302,7 @@ def exception(self): return self._exception -class _MessagingService(object): +class _MessagingService: """Service class that implements Firebase Cloud Messaging (FCM) functionality.""" FCM_URL = 'https://fcm.googleapis.com/v1/projects/{0}/messages:send' @@ -342,6 +342,7 @@ def encode_message(cls, message): return cls.JSON_ENCODER.default(message) def send(self, message, dry_run=False): + """Sends the given message to FCM via the FCM v1 API.""" data = self._message_data(message, dry_run) try: resp = self._client.body( diff --git a/firebase_admin/project_management.py b/firebase_admin/project_management.py index 68e10797c..076542bda 100644 --- a/firebase_admin/project_management.py +++ b/firebase_admin/project_management.py @@ -140,7 +140,7 @@ def _check_not_none(obj, field_name): return obj -class AndroidApp(object): +class AndroidApp: """A reference to an Android app within a Firebase project. Note: Unless otherwise specified, all methods defined in this class make an RPC. @@ -238,7 +238,7 @@ def delete_sha_certificate(self, certificate_to_delete): return self._service.delete_sha_certificate(certificate_to_delete) -class IOSApp(object): +class IOSApp: """A reference to an iOS app within a Firebase project. Note: Unless otherwise specified, all methods defined in this class make an RPC. @@ -294,7 +294,7 @@ def get_config(self): return self._service.get_ios_app_config(self._app_id) -class _AppMetadata(object): +class _AppMetadata: """Detailed information about a Firebase Android or iOS app.""" def __init__(self, name, app_id, display_name, project_id): @@ -382,7 +382,7 @@ def __hash__(self): return hash((self._name, self.app_id, self.display_name, self.project_id, self.bundle_id)) -class SHACertificate(object): +class SHACertificate: """Represents a SHA-1 or SHA-256 certificate associated with an Android app.""" SHA_1 = 'SHA_1' @@ -456,7 +456,7 @@ def __hash__(self): return hash((self.name, self.sha_hash, self.cert_type)) -class _ProjectManagementService(object): +class _ProjectManagementService: """Provides methods for interacting with the Firebase Project Management Service.""" BASE_URL = 'https://firebase.googleapis.com' @@ -613,10 +613,10 @@ def _poll_app_creation(self, operation_name): response = poll_response.get('response') if response: return response - else: - raise exceptions.UnknownError( - 'Polling finished, but the operation terminated in an error.', - http_response=http_response) + + raise exceptions.UnknownError( + 'Polling finished, but the operation terminated in an error.', + http_response=http_response) raise exceptions.DeadlineExceededError('Polling deadline exceeded.') def get_android_app_config(self, app_id): diff --git a/firebase_admin/storage.py b/firebase_admin/storage.py index 6aab1ebc1..a080b31ef 100644 --- a/firebase_admin/storage.py +++ b/firebase_admin/storage.py @@ -54,7 +54,7 @@ def bucket(name=None, app=None): return client.bucket(name) -class _StorageClient(object): +class _StorageClient: """Holds a Google Cloud Storage client instance.""" def __init__(self, credentials, project, default_bucket): @@ -77,7 +77,7 @@ def bucket(self, name=None): 'Storage bucket name not specified. Specify the bucket name via the ' '"storageBucket" option when initializing the App, or specify the bucket ' 'name explicitly when calling the storage.bucket() function.') - elif not bucket_name or not isinstance(bucket_name, six.string_types): + if not bucket_name or not isinstance(bucket_name, six.string_types): raise ValueError( 'Invalid storage bucket name: "{0}". Bucket name must be a non-empty ' 'string.'.format(bucket_name)) diff --git a/integration/test_auth.py b/integration/test_auth.py index 9d5d0dfe3..c3759ce12 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -18,16 +18,16 @@ import random import time import uuid -import six +import google.oauth2.credentials +from google.auth import transport import pytest import requests +import six import firebase_admin from firebase_admin import auth from firebase_admin import credentials -import google.oauth2.credentials -from google.auth import transport _verify_token_url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken' @@ -263,7 +263,7 @@ def test_create_user(new_user): assert user.custom_claims is None assert user.user_metadata.creation_timestamp > 0 assert user.user_metadata.last_sign_in_timestamp is None - assert len(user.provider_data) is 0 + assert len(user.provider_data) == 0 with pytest.raises(auth.UidAlreadyExistsError): auth.create_user(uid=new_user.uid) diff --git a/integration/test_db.py b/integration/test_db.py index 4c2f6bde2..abd02660f 100644 --- a/integration/test_db.py +++ b/integration/test_db.py @@ -31,8 +31,8 @@ def integration_conf(request): host_override = os.environ.get('FIREBASE_DATABASE_EMULATOR_HOST') if host_override: return None, 'fake-project-id' - else: - return conftest.integration_conf(request) + + return conftest.integration_conf(request) @pytest.fixture(scope='module') @@ -83,7 +83,7 @@ def testref(update_rules, testdata, app): return ref -class TestReferenceAttributes(object): +class TestReferenceAttributes: """Test cases for attributes exposed by db.Reference class.""" def test_ref_attributes(self, testref): @@ -101,7 +101,7 @@ def test_parent(self, testref): assert parent.path == '/_adminsdk/python' -class TestReadOperations(object): +class TestReadOperations: """Test cases for reading node values.""" def test_get_value(self, testref, testdata): @@ -143,7 +143,7 @@ def test_get_nonexisting_child_value(self, testref): assert testref.child('none_existing').get() is None -class TestWriteOperations(object): +class TestWriteOperations: """Test cases for creating and updating node values.""" def test_push(self, testref): @@ -247,7 +247,7 @@ def test_delete(self, testref): assert ref.get() is None -class TestAdvancedQueries(object): +class TestAdvancedQueries: """Test cases for advanced interactions via the db.Query interface.""" height_sorted = [ @@ -352,7 +352,7 @@ def none_override_app(request, update_rules): firebase_admin.delete_app(app) -class TestAuthVariableOverride(object): +class TestAuthVariableOverride: """Test cases for database auth variable overrides.""" def init_ref(self, path, app): diff --git a/integration/test_messaging.py b/integration/test_messaging.py index bc4f1d1ca..001b96a0a 100644 --- a/integration/test_messaging.py +++ b/integration/test_messaging.py @@ -140,7 +140,7 @@ def test_send_multicast(): batch_response = messaging.send_multicast(multicast) - assert batch_response.success_count is 0 + assert batch_response.success_count == 0 assert batch_response.failure_count == 2 assert len(batch_response.responses) == 2 for response in batch_response.responses: diff --git a/lint.sh b/lint.sh index aeb37f741..fe695c293 100755 --- a/lint.sh +++ b/lint.sh @@ -32,7 +32,7 @@ set -o errexit set -o nounset SKIP_FOR_TESTS="redefined-outer-name,protected-access,missing-docstring,too-many-lines" -SKIP_FOR_SNIPPETS="${SKIP_FOR_TESTS},reimported,unused-variable" +SKIP_FOR_SNIPPETS="${SKIP_FOR_TESTS},reimported,unused-variable,unused-import,import-outside-toplevel" if [[ "$#" -eq 1 && "$1" = "all" ]] then diff --git a/requirements.txt b/requirements.txt index 2f1a09a5b..a3c483e25 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -pylint == 1.6.4 +pylint == 2.4.4 pytest >= 3.6.0 pytest-cov >= 2.4.0 pytest-localserver >= 0.4.1 diff --git a/snippets/auth/index.py b/snippets/auth/index.py index 552875696..b1c091064 100644 --- a/snippets/auth/index.py +++ b/snippets/auth/index.py @@ -385,8 +385,8 @@ def serve_content_for_admin(decoded_claims): # Check custom claims to confirm user is an admin. if decoded_claims.get('admin') is True: return serve_content_for_admin(decoded_claims) - else: - return flask.abort(401, 'Insufficient permissions') + + return flask.abort(401, 'Insufficient permissions') except auth.InvalidSessionCookieError: # Session cookie is invalid, expired or revoked. Force user to login. return flask.redirect('/login') diff --git a/tests/test_app.py b/tests/test_app.py index 9d3446692..fe3a43a5c 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -30,7 +30,7 @@ # This fixture will ignore the environment variable pointing to the default # configuration for the duration of the tests. -class CredentialProvider(object): +class CredentialProvider: def init(self): pass @@ -73,7 +73,7 @@ def get(self): return None -class AppService(object): +class AppService: def __init__(self, app): self._app = app @@ -89,8 +89,8 @@ def app_credential(request): def init_app(request): if request.param: return firebase_admin.initialize_app(CREDENTIAL, name=request.param) - else: - return firebase_admin.initialize_app(CREDENTIAL) + + return firebase_admin.initialize_app(CREDENTIAL) @pytest.fixture(scope="function") def env_test_case(request): @@ -211,7 +211,7 @@ def revert_config_env(config_old): elif os.environ.get(CONFIG_JSON) is not None: del os.environ[CONFIG_JSON] -class TestFirebaseApp(object): +class TestFirebaseApp: """Test cases for App initialization and life cycle.""" invalid_credentials = ['', 'foo', 0, 1, dict(), list(), tuple(), True, False] diff --git a/tests/test_credentials.py b/tests/test_credentials.py index 6f081d796..d78ef5192 100644 --- a/tests/test_credentials.py +++ b/tests/test_credentials.py @@ -22,9 +22,9 @@ from google.auth import exceptions from google.oauth2 import credentials as gcredentials from google.oauth2 import service_account -from firebase_admin import credentials import pytest +from firebase_admin import credentials from tests import testutils @@ -33,7 +33,7 @@ def check_scopes(g_credential): assert sorted(credentials._scopes) == sorted(g_credential.scopes) -class TestCertificate(object): +class TestCertificate: invalid_certs = { 'NonExistingFile': ('non_existing.json', IOError), @@ -91,7 +91,7 @@ def app_default(request): del os.environ[var_name] -class TestApplicationDefault(object): +class TestApplicationDefault: @pytest.mark.parametrize('app_default', [testutils.resource_filename('service_account.json')], indirect=True) @@ -122,7 +122,7 @@ def test_nonexisting_path(self, app_default): creds.get_credential() # This now throws. -class TestRefreshToken(object): +class TestRefreshToken: def test_init_from_file(self): credential = credentials.RefreshToken( diff --git a/tests/test_db.py b/tests/test_db.py index e9f8f7dda..b20f99cb9 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -48,7 +48,7 @@ def send(self, request, **kwargs): return resp -class MockSSEClient(object): +class MockSSEClient: """A mock SSE client that mimics long-lived HTTP connections.""" def __init__(self, events): @@ -62,11 +62,11 @@ def close(self): self.closed = True -class _Object(object): +class _Object: pass -class TestReferencePath(object): +class TestReferencePath: """Test cases for Reference paths.""" # path => (fullstr, key, parent) @@ -127,7 +127,7 @@ def test_invalid_child(self, child): parent.child(child) -class _RefOperations(object): +class _RefOperations: """A collection of operations that can be performed using a ``db.Reference``. This can be used to test any functionality that is common across multiple API calls. @@ -159,7 +159,7 @@ def get_ops(cls): return [cls.get, cls.push, cls.set, cls.delete, cls.query] -class TestReference(object): +class TestReference: """Test cases for database queries via References.""" test_url = 'https://test.firebaseio.com' @@ -381,7 +381,7 @@ def test_set_invalid_update(self, update): recorder = self.instrument(ref, '') with pytest.raises(ValueError): ref.update(update) - assert len(recorder) is 0 + assert len(recorder) == 0 @pytest.mark.parametrize('data', valid_values) def test_push(self, data): @@ -527,7 +527,7 @@ def test_other_error(self, error_code, func): assert excinfo.value.http_response is not None -class TestListenerRegistration(object): +class TestListenerRegistration: """Test cases for receiving events via ListenerRegistrations.""" def test_listen_error(self): @@ -598,7 +598,7 @@ def wait_for(cls, events, count=1, timeout_seconds=5): raise pytest.fail('Timed out while waiting for events') -class TestReferenceWithAuthOverride(object): +class TestReferenceWithAuthOverride: """Test cases for database queries via References.""" test_url = 'https://test.firebaseio.com' @@ -671,7 +671,7 @@ def test_range_query(self): assert recorder[0].headers['User-Agent'] == db._USER_AGENT -class TestDatabaseInitialization(object): +class TestDatabaseInitialization: """Test cases for database initialization.""" def teardown_method(self): @@ -847,13 +847,13 @@ def initquery(request): ref = db.Reference(path='foo') if request.param == '$key': return ref.order_by_key(), request.param - elif request.param == '$value': + if request.param == '$value': return ref.order_by_value(), request.param - else: - return ref.order_by_child(request.param), request.param + return ref.order_by_child(request.param), request.param -class TestQuery(object): + +class TestQuery: """Test cases for db.Query class.""" valid_paths = { @@ -982,7 +982,7 @@ def test_invalid_query_args(self): db.Query(order_by='$key', client=ref._client, pathurl=ref._add_suffix(), foo='bar') -class TestSorter(object): +class TestSorter: """Test cases for db._Sorter class.""" value_test_cases = [ diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 98d9ce5e9..3df7ec2e3 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -37,7 +37,7 @@ }) -class TestRequests(object): +class TestRequests: def test_timeout_error(self): error = requests.exceptions.Timeout('Test error') @@ -156,7 +156,6 @@ def test_handle_platform_error_with_custom_handler_ignore(self): def _custom_handler(cause, message, error_dict): invocations.append((cause, message, error_dict)) - return None firebase_error = _utils.handle_platform_error_from_requests(error, _custom_handler) @@ -180,7 +179,7 @@ def _create_response(self, status=500, payload=None): return resp, exc -class TestGoogleApiClient(object): +class TestGoogleApiClient: @pytest.mark.parametrize('error', [ socket.timeout('Test error'), @@ -313,7 +312,6 @@ def test_handle_platform_error_with_custom_handler_ignore(self): def _custom_handler(cause, message, error_dict, http_response): invocations.append((cause, message, error_dict, http_response)) - return None firebase_error = _utils.handle_platform_error_from_googleapiclient(error, _custom_handler) diff --git a/tests/test_firestore.py b/tests/test_firestore.py index 01b019333..768eb637e 100644 --- a/tests/test_firestore.py +++ b/tests/test_firestore.py @@ -30,7 +30,7 @@ @pytest.mark.skipif( platform.python_implementation() == 'PyPy', reason='Firestore is not supported on PyPy') -class TestFirestore(object): +class TestFirestore: """Test class Firestore APIs.""" def teardown_method(self, method): diff --git a/tests/test_http_client.py b/tests/test_http_client.py index a0c6dc654..ce35e5ce4 100644 --- a/tests/test_http_client.py +++ b/tests/test_http_client.py @@ -84,7 +84,7 @@ def _instrument(client, payload, status=200): return recorder -class TestHttpRetry(object): +class TestHttpRetry: """Unit tests for the default HTTP retry configuration.""" ENTITY_ENCLOSING_METHODS = ['post', 'put', 'patch'] diff --git a/tests/test_instance_id.py b/tests/test_instance_id.py index 83e66491a..a13506a07 100644 --- a/tests/test_instance_id.py +++ b/tests/test_instance_id.py @@ -50,7 +50,7 @@ exceptions.UnavailableError), } -class TestDeleteInstanceId(object): +class TestDeleteInstanceId: def teardown_method(self): testutils.cleanup_apps() @@ -132,4 +132,4 @@ def test_invalid_instance_id(self, iid): _, recorder = self._instrument_iid_service(app) with pytest.raises(ValueError): instance_id.delete_instance_id(iid) - assert len(recorder) is 0 + assert len(recorder) == 0 diff --git a/tests/test_messaging.py b/tests/test_messaging.py index 96b512577..36f5943be 100644 --- a/tests/test_messaging.py +++ b/tests/test_messaging.py @@ -62,7 +62,7 @@ def check_exception(exception, message, status): assert exception.http_response.status_code == status -class TestMessageStr(object): +class TestMessageStr: @pytest.mark.parametrize('msg', [ messaging.Message(), @@ -90,7 +90,7 @@ def test_data_message(self): 'k1': 'v1', 'k2': 'v2'})) == '{"data": {"k1": "v1", "k2": "v2"}, "topic": "topic"}' -class TestMulticastMessage(object): +class TestMulticastMessage: @pytest.mark.parametrize('tokens', NON_LIST_ARGS) def test_invalid_tokens_type(self, tokens): @@ -117,7 +117,7 @@ def test_tokens_type(self): assert len(message.tokens) == 500 -class TestMessageEncoder(object): +class TestMessageEncoder: @pytest.mark.parametrize('msg', [ messaging.Message(), @@ -183,7 +183,7 @@ def test_fcm_options(self): {'topic': 'topic'}) -class TestNotificationEncoder(object): +class TestNotificationEncoder: @pytest.mark.parametrize('data', NON_OBJECT_ARGS) def test_invalid_notification(self, data): @@ -219,7 +219,7 @@ def test_notification_message(self): {'topic': 'topic', 'notification': {'title': 't'}}) -class TestFcmOptionEncoder(object): +class TestFcmOptionEncoder: @pytest.mark.parametrize('label', [ '!', @@ -266,7 +266,7 @@ def test_fcm_options(self): }) -class TestAndroidConfigEncoder(object): +class TestAndroidConfigEncoder: @pytest.mark.parametrize('data', NON_OBJECT_ARGS) def test_invalid_android(self, data): @@ -367,7 +367,7 @@ def test_android_ttl(self, ttl): check_encoding(msg, expected) -class TestAndroidNotificationEncoder(object): +class TestAndroidNotificationEncoder: def _check_notification(self, notification): with pytest.raises(ValueError) as excinfo: @@ -603,7 +603,7 @@ def test_android_notification(self): check_encoding(msg, expected) -class TestLightSettingsEncoder(object): +class TestLightSettingsEncoder: def _check_light_settings(self, light_settings): with pytest.raises(ValueError) as excinfo: @@ -717,7 +717,7 @@ def test_light_settings(self): check_encoding(msg, expected) -class TestWebpushConfigEncoder(object): +class TestWebpushConfigEncoder: @pytest.mark.parametrize('data', NON_OBJECT_ARGS) def test_invalid_webpush(self, data): @@ -763,7 +763,7 @@ def test_webpush_config(self): check_encoding(msg, expected) -class TestWebpushFCMOptionsEncoder(object): +class TestWebpushFCMOptionsEncoder: @pytest.mark.parametrize('data', NON_OBJECT_ARGS) def test_invalid_webpush_fcm_options(self, data): @@ -809,7 +809,7 @@ def test_webpush_options(self): check_encoding(msg, expected) -class TestWebpushNotificationEncoder(object): +class TestWebpushNotificationEncoder: def _check_notification(self, notification): with pytest.raises(ValueError) as excinfo: @@ -1012,7 +1012,7 @@ def test_invalid_action_icon(self, data): assert str(excinfo.value) == 'WebpushNotificationAction.icon must be a string.' -class TestAPNSConfigEncoder(object): +class TestAPNSConfigEncoder: @pytest.mark.parametrize('data', NON_OBJECT_ARGS) def test_invalid_apns(self, data): @@ -1051,7 +1051,7 @@ def test_apns_config(self): check_encoding(msg, expected) -class TestAPNSPayloadEncoder(object): +class TestAPNSPayloadEncoder: @pytest.mark.parametrize('data', NON_OBJECT_ARGS) def test_invalid_payload(self, data): @@ -1085,7 +1085,7 @@ def test_apns_payload(self): check_encoding(msg, expected) -class TestApsEncoder(object): +class TestApsEncoder: def _encode_aps(self, aps): return check_encoding(messaging.Message( @@ -1227,7 +1227,7 @@ def test_aps_custom_data(self): check_encoding(msg, expected) -class TestApsSoundEncoder(object): +class TestApsSoundEncoder: def _check_sound(self, sound): with pytest.raises(ValueError) as excinfo: @@ -1335,7 +1335,7 @@ def test_critical_sound_name_only(self): check_encoding(msg, expected) -class TestApsAlertEncoder(object): +class TestApsAlertEncoder: def _check_alert(self, alert): with pytest.raises(ValueError) as excinfo: @@ -1539,7 +1539,7 @@ def test_aps_alert_custom_data_override(self): } check_encoding(msg, expected) -class TestTimeout(object): +class TestTimeout: @classmethod def setup_class(cls): @@ -1577,7 +1577,7 @@ def test_topic_management_timeout(self): assert self.recorder[0]._extra_kwargs['timeout'] == pytest.approx(4, 0.001) -class TestSend(object): +class TestSend: _DEFAULT_RESPONSE = json.dumps({'name': 'message-id'}) _CLIENT_VERSION = 'fire-admin-python/{0}'.format(firebase_admin.__version__) @@ -1753,7 +1753,7 @@ def test_send_unknown_fcm_error_code(self, status): assert json.loads(recorder[0].body.decode()) == body -class TestBatch(object): +class TestBatch: @classmethod def setup_class(cls): @@ -1822,8 +1822,8 @@ def test_send_all(self): payload=self._batch_payload([(200, payload), (200, payload)])) msg = messaging.Message(topic='foo') batch_response = messaging.send_all([msg, msg], dry_run=True) - assert batch_response.success_count is 2 - assert batch_response.failure_count is 0 + assert batch_response.success_count == 2 + assert batch_response.failure_count == 0 assert len(batch_response.responses) == 2 assert [r.message_id for r in batch_response.responses] == ['message-id', 'message-id'] assert all([r.success for r in batch_response.responses]) @@ -1842,8 +1842,8 @@ def test_send_all_detailed_error(self, status): payload=self._batch_payload([(200, success_payload), (status, error_payload)])) msg = messaging.Message(topic='foo') batch_response = messaging.send_all([msg, msg]) - assert batch_response.success_count is 1 - assert batch_response.failure_count is 1 + assert batch_response.success_count == 1 + assert batch_response.failure_count == 1 assert len(batch_response.responses) == 2 success_response = batch_response.responses[0] assert success_response.message_id == 'message-id' @@ -1869,8 +1869,8 @@ def test_send_all_canonical_error_code(self, status): payload=self._batch_payload([(200, success_payload), (status, error_payload)])) msg = messaging.Message(topic='foo') batch_response = messaging.send_all([msg, msg]) - assert batch_response.success_count is 1 - assert batch_response.failure_count is 1 + assert batch_response.success_count == 1 + assert batch_response.failure_count == 1 assert len(batch_response.responses) == 2 success_response = batch_response.responses[0] assert success_response.message_id == 'message-id' @@ -1903,8 +1903,8 @@ def test_send_all_fcm_error_code(self, status, fcm_error_code, exc_type): payload=self._batch_payload([(200, success_payload), (status, error_payload)])) msg = messaging.Message(topic='foo') batch_response = messaging.send_all([msg, msg]) - assert batch_response.success_count is 1 - assert batch_response.failure_count is 1 + assert batch_response.success_count == 1 + assert batch_response.failure_count == 1 assert len(batch_response.responses) == 2 success_response = batch_response.responses[0] assert success_response.message_id == 'message-id' @@ -1997,8 +1997,8 @@ def test_send_multicast(self): payload=self._batch_payload([(200, payload), (200, payload)])) msg = messaging.MulticastMessage(tokens=['foo', 'foo']) batch_response = messaging.send_multicast(msg, dry_run=True) - assert batch_response.success_count is 2 - assert batch_response.failure_count is 0 + assert batch_response.success_count == 2 + assert batch_response.failure_count == 0 assert len(batch_response.responses) == 2 assert [r.message_id for r in batch_response.responses] == ['message-id', 'message-id'] assert all([r.success for r in batch_response.responses]) @@ -2017,8 +2017,8 @@ def test_send_multicast_detailed_error(self, status): payload=self._batch_payload([(200, success_payload), (status, error_payload)])) msg = messaging.MulticastMessage(tokens=['foo', 'foo']) batch_response = messaging.send_multicast(msg) - assert batch_response.success_count is 1 - assert batch_response.failure_count is 1 + assert batch_response.success_count == 1 + assert batch_response.failure_count == 1 assert len(batch_response.responses) == 2 success_response = batch_response.responses[0] assert success_response.message_id == 'message-id' @@ -2045,8 +2045,8 @@ def test_send_multicast_canonical_error_code(self, status): payload=self._batch_payload([(200, success_payload), (status, error_payload)])) msg = messaging.MulticastMessage(tokens=['foo', 'foo']) batch_response = messaging.send_multicast(msg) - assert batch_response.success_count is 1 - assert batch_response.failure_count is 1 + assert batch_response.success_count == 1 + assert batch_response.failure_count == 1 assert len(batch_response.responses) == 2 success_response = batch_response.responses[0] assert success_response.message_id == 'message-id' @@ -2079,8 +2079,8 @@ def test_send_multicast_fcm_error_code(self, status): payload=self._batch_payload([(200, success_payload), (status, error_payload)])) msg = messaging.MulticastMessage(tokens=['foo', 'foo']) batch_response = messaging.send_multicast(msg) - assert batch_response.success_count is 1 - assert batch_response.failure_count is 1 + assert batch_response.success_count == 1 + assert batch_response.failure_count == 1 assert len(batch_response.responses) == 2 success_response = batch_response.responses[0] assert success_response.message_id == 'message-id' @@ -2152,7 +2152,7 @@ def test_send_multicast_batch_fcm_error_code(self, status): check_exception(excinfo.value, 'test error', status) -class TestTopicManagement(object): +class TestTopicManagement: _DEFAULT_RESPONSE = json.dumps({'results': [{}, {'error': 'error_reason'}]}) _DEFAULT_ERROR_RESPONSE = json.dumps({'error': 'error_reason'}) diff --git a/tests/test_project_management.py b/tests/test_project_management.py index e8353e212..aa717bbf7 100644 --- a/tests/test_project_management.py +++ b/tests/test_project_management.py @@ -202,7 +202,7 @@ NOT_FOUND_RESPONSE = '{"error": {"message": "Failed to find the resource"}}' UNAVAILABLE_RESPONSE = '{"error": {"message": "Backend servers are over capacity"}}' -class TestAndroidAppMetadata(object): +class TestAndroidAppMetadata: def test_create_android_app_metadata_errors(self): # package_name must be a non-empty string. @@ -289,7 +289,6 @@ def test_android_app_metadata_eq_and_hash(self): # Don't trigger __ne__. assert not metadata_1 == ios_metadata # pylint: disable=unneeded-not assert metadata_1 != ios_metadata - assert metadata_1 == metadata_1 assert metadata_1 != metadata_2 assert metadata_1 != metadata_3 assert metadata_1 != metadata_4 @@ -315,7 +314,7 @@ def test_android_app_metadata_project_id(self): assert ANDROID_APP_METADATA.project_id == 'test-project-id' -class TestIOSAppMetadata(object): +class TestIOSAppMetadata: def test_create_ios_app_metadata_errors(self): # bundle_id must be a non-empty string. @@ -402,7 +401,6 @@ def test_ios_app_metadata_eq_and_hash(self): # Don't trigger __ne__. assert not metadata_1 == android_metadata # pylint: disable=unneeded-not assert metadata_1 != android_metadata - assert metadata_1 == metadata_1 assert metadata_1 != metadata_2 assert metadata_1 != metadata_3 assert metadata_1 != metadata_4 @@ -427,7 +425,7 @@ def test_ios_app_metadata_project_id(self): assert IOS_APP_METADATA.project_id == 'test-project-id' -class TestSHACertificate(object): +class TestSHACertificate: def test_create_sha_certificate_errors(self): # sha_hash cannot be None. with pytest.raises(ValueError): @@ -469,7 +467,6 @@ def test_sha_certificate_eq(self): 'cert_type': 'SHA_1', } - assert sha_cert_1 == sha_cert_1 assert sha_cert_1 != sha_cert_2 assert sha_cert_1 != sha_cert_3 assert sha_cert_1 != sha_cert_4 @@ -496,7 +493,7 @@ def test_sha_certificate_cert_type(self): assert SHA_256_CERTIFICATE.cert_type == 'SHA_256' -class BaseProjectManagementTest(object): +class BaseProjectManagementTest: @classmethod def setup_class(cls): project_management._ProjectManagementService.POLL_BASE_WAIT_TIME_SECONDS = 0.01 diff --git a/tests/test_sseclient.py b/tests/test_sseclient.py index a9ec2edf7..881ecc6b9 100644 --- a/tests/test_sseclient.py +++ b/tests/test_sseclient.py @@ -36,7 +36,7 @@ def send(self, request, **kwargs): return resp -class TestSSEClient(object): +class TestSSEClient: """Test cases for the SSEClient""" test_url = "https://test.firebaseio.com" @@ -54,7 +54,7 @@ def test_init_sseclient(self): payload = 'event: put\ndata: {"path":"/","data":"testevent"}\n\n' sseclient = self.init_sse(payload) assert sseclient.url == self.test_url - assert sseclient.session != None + assert sseclient.session is not None def test_single_event(self): payload = 'event: put\ndata: {"path":"/","data":"testevent"}\n\n' @@ -120,7 +120,7 @@ def test_event_separators(self): assert len(recorder) == 1 -class TestEvent(object): +class TestEvent: """Test cases for server-side events""" def test_normal(self): diff --git a/tests/test_token_gen.py b/tests/test_token_gen.py index e016b8fb1..e92fd0059 100644 --- a/tests/test_token_gen.py +++ b/tests/test_token_gen.py @@ -166,7 +166,7 @@ def revoked_tokens(): return json.dumps(mock_user) -class TestCreateCustomToken(object): +class TestCreateCustomToken: valid_args = { 'Basic': (MOCK_UID, {'one': 2, 'three': 'four'}), @@ -283,7 +283,7 @@ def _verify_signer(self, token, signer): assert body['sub'] == signer -class TestCreateSessionCookie(object): +class TestCreateSessionCookie: @pytest.mark.parametrize('id_token', [None, '', 0, 1, True, False, list(), dict(), tuple()]) def test_invalid_id_token(self, user_mgt_app, id_token): @@ -350,7 +350,7 @@ def test_unexpected_response(self, user_mgt_app): TEST_SESSION_COOKIE = _get_session_cookie() -class TestVerifyIdToken(object): +class TestVerifyIdToken: valid_tokens = { 'BinaryToken': TEST_ID_TOKEN, @@ -475,7 +475,7 @@ def test_certificate_request_failure(self, user_mgt_app): assert excinfo.value.http_response is None -class TestVerifySessionCookie(object): +class TestVerifySessionCookie: valid_cookies = { 'BinaryCookie': TEST_SESSION_COOKIE, @@ -590,7 +590,7 @@ def test_certificate_request_failure(self, user_mgt_app): assert excinfo.value.http_response is None -class TestCertificateCaching(object): +class TestCertificateCaching: def test_certificate_caching(self, user_mgt_app, httpserver): httpserver.serve_content(MOCK_PUBLIC_CERTS, 200, headers={'Cache-Control': 'max-age=3600'}) diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index f4e03cc3f..f1572baf2 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -19,6 +19,7 @@ import time import pytest +from six.moves import urllib import firebase_admin from firebase_admin import auth @@ -28,8 +29,6 @@ from firebase_admin import _user_mgt from tests import testutils -from six.moves import urllib - INVALID_STRINGS = [None, '', 0, 1, True, False, list(), tuple(), dict()] INVALID_DICTS = [None, 'foo', 0, 1, True, False, list(), tuple()] @@ -101,7 +100,7 @@ def _check_user_record(user, expected_uid='testuser'): assert provider.provider_id == 'phone' -class TestAuthServiceInitialization(object): +class TestAuthServiceInitialization: def test_fail_on_no_project_id(self): app = firebase_admin.initialize_app(testutils.MockCredential(), name='userMgt2') @@ -109,7 +108,7 @@ def test_fail_on_no_project_id(self): auth._get_auth_service(app) firebase_admin.delete_app(app) -class TestUserRecord(object): +class TestUserRecord: # Input dict must be non-empty, and must not contain unsupported keys. @pytest.mark.parametrize('data', INVALID_DICTS + [{}, {'foo':'bar'}]) @@ -186,10 +185,10 @@ def test_tokens_valid_after_time(self): def test_no_tokens_valid_after_time(self): user = auth.UserRecord({'localId' : 'user'}) - assert user.tokens_valid_after_timestamp is 0 + assert user.tokens_valid_after_timestamp == 0 -class TestGetUser(object): +class TestGetUser: @pytest.mark.parametrize('arg', INVALID_STRINGS + ['a'*129]) def test_invalid_get_user(self, arg, user_mgt_app): @@ -295,7 +294,7 @@ def test_get_user_by_phone_http_error(self, user_mgt_app): assert excinfo.value.cause is not None -class TestCreateUser(object): +class TestCreateUser: already_exists_errors = { 'DUPLICATE_EMAIL': auth.EmailAlreadyExistsError, @@ -395,7 +394,7 @@ def test_create_user_unexpected_response(self, user_mgt_app): assert isinstance(excinfo.value, exceptions.UnknownError) -class TestUpdateUser(object): +class TestUpdateUser: @pytest.mark.parametrize('arg', INVALID_STRINGS + ['a'*129]) def test_invalid_uid(self, user_mgt_app, arg): @@ -513,7 +512,7 @@ def test_update_user_valid_since(self, user_mgt_app, arg): assert request == {'localId': 'testuser', 'validSince': int(arg)} -class TestSetCustomUserClaims(object): +class TestSetCustomUserClaims: @pytest.mark.parametrize('arg', INVALID_STRINGS + ['a'*129]) def test_invalid_uid(self, user_mgt_app, arg): @@ -576,7 +575,7 @@ def test_set_custom_user_claims_error(self, user_mgt_app): assert excinfo.value.cause is not None -class TestDeleteUser(object): +class TestDeleteUser: @pytest.mark.parametrize('arg', INVALID_STRINGS + ['a'*129]) def test_invalid_delete_user(self, user_mgt_app, arg): @@ -606,7 +605,7 @@ def test_delete_user_unexpected_response(self, user_mgt_app): assert isinstance(excinfo.value, exceptions.UnknownError) -class TestListUsers(object): +class TestListUsers: @pytest.mark.parametrize('arg', [None, 'foo', list(), dict(), 0, -1, 1001, False]) def test_invalid_max_results(self, user_mgt_app, arg): @@ -625,7 +624,7 @@ def test_list_single_page(self, user_mgt_app): assert page.next_page_token == '' assert page.has_next_page is False assert page.get_next_page() is None - users = [user for user in page.iterate_all()] + users = list(user for user in page.iterate_all()) assert len(users) == 2 self._check_rpc_calls(recorder) @@ -710,7 +709,7 @@ def test_list_users_stop_iteration(self, user_mgt_app): assert len(page.users) == 3 iterator = page.iterate_all() - users = [user for user in iterator] + users = list(user for user in iterator) assert len(page.users) == 3 with pytest.raises(StopIteration): next(iterator) @@ -721,9 +720,9 @@ def test_list_users_no_users_response(self, user_mgt_app): response = {'users': []} _instrument_user_manager(user_mgt_app, 200, json.dumps(response)) page = auth.list_users(app=user_mgt_app) - assert len(page.users) is 0 - users = [user for user in page.iterate_all()] - assert len(users) is 0 + assert len(page.users) == 0 + users = list(user for user in page.iterate_all()) + assert len(users) == 0 def test_list_users_with_max_results(self, user_mgt_app): _, recorder = _instrument_user_manager(user_mgt_app, 200, MOCK_LIST_USERS_RESPONSE) @@ -777,7 +776,7 @@ def _check_rpc_calls(self, recorder, expected=None): assert request == expected -class TestUserProvider(object): +class TestUserProvider: _INVALID_PROVIDERS = ( [{'display_name': arg} for arg in INVALID_STRINGS[1:]] + @@ -819,7 +818,7 @@ def test_invalid_arg(self, arg): auth.UserProvider(uid='test', provider_id='google.com', **arg) -class TestUserMetadata(object): +class TestUserMetadata: _INVALID_ARGS = ( [{'creation_timestamp': arg} for arg in INVALID_TIMESTAMPS] + @@ -832,7 +831,7 @@ def test_invalid_args(self, arg): auth.UserMetadata(**arg) -class TestImportUserRecord(object): +class TestImportUserRecord: _INVALID_USERS = ( [{'display_name': arg} for arg in INVALID_STRINGS[1:]] + @@ -908,7 +907,7 @@ def test_disabled(self, disabled): assert user.to_dict() == {'localId': 'test', 'disabled': disabled} -class TestUserImportHash(object): +class TestUserImportHash: @pytest.mark.parametrize('func,name', [ (auth.UserImportHash.hmac_sha512, 'HMAC_SHA512'), @@ -1021,7 +1020,7 @@ def test_invalid_standard_scrypt(self, arg): auth.UserImportHash.standard_scrypt(**params) -class TestImportUsers(object): +class TestImportUsers: @pytest.mark.parametrize('arg', [None, list(), tuple(), dict(), 0, 1, 'foo']) def test_invalid_users(self, user_mgt_app, arg): @@ -1041,7 +1040,7 @@ def test_import_users(self, user_mgt_app): ] result = auth.import_users(users, app=user_mgt_app) assert result.success_count == 2 - assert result.failure_count is 0 + assert result.failure_count == 0 assert result.errors == [] expected = {'users': [{'localId': 'user1'}, {'localId': 'user2'}]} self._check_rpc_calls(recorder, expected) @@ -1087,7 +1086,7 @@ def test_import_users_with_hash(self, user_mgt_app): b'key', rounds=8, memory_cost=14, salt_separator=b'sep') result = auth.import_users(users, hash_alg=hash_alg, app=user_mgt_app) assert result.success_count == 2 - assert result.failure_count is 0 + assert result.failure_count == 0 assert result.errors == [] expected = { 'users': [ @@ -1127,7 +1126,7 @@ def _check_rpc_calls(self, recorder, expected): assert request == expected -class TestRevokeRefreshTokkens(object): +class TestRevokeRefreshTokkens: def test_revoke_refresh_tokens(self, user_mgt_app): _, recorder = _instrument_user_manager(user_mgt_app, 200, '{"localId":"testuser"}') @@ -1141,7 +1140,7 @@ def test_revoke_refresh_tokens(self, user_mgt_app): assert int(request['validSince']) <= int(after_time) -class TestActionCodeSetting(object): +class TestActionCodeSetting: def test_valid_data(self): data = { @@ -1186,7 +1185,7 @@ def test_encode_action_code_bad_data(self): _user_mgt.encode_action_code_settings({"foo":"bar"}) -class TestGenerateEmailActionLink(object): +class TestGenerateEmailActionLink: def test_email_verification_no_settings(self, user_mgt_app): _, recorder = _instrument_user_manager(user_mgt_app, 200, '{"oobLink":"https://testlink"}') diff --git a/tests/testutils.py b/tests/testutils.py index cdbf75aef..9c69663a0 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -88,7 +88,7 @@ def __init__(self, status, response): self.response = MockResponse(status, response) self.log = [] - def __call__(self, *args, **kwargs): + def __call__(self, *args, **kwargs): # pylint: disable=arguments-differ self.log.append((args, kwargs)) return self.response @@ -100,7 +100,7 @@ def __init__(self, error): self.error = error self.log = [] - def __call__(self, *args, **kwargs): + def __call__(self, *args, **kwargs): # pylint: disable=arguments-differ self.log.append((args, kwargs)) raise self.error @@ -139,7 +139,7 @@ def __init__(self, responses, statuses, recorder): self._statuses = list(statuses) self._recorder = recorder - def send(self, request, **kwargs): + def send(self, request, **kwargs): # pylint: disable=arguments-differ request._extra_kwargs = kwargs self._recorder.append(request) resp = models.Response() From bcdc4c2b31d9a915a19279102e7e1601c86a2b45 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Wed, 8 Jan 2020 16:23:05 -0800 Subject: [PATCH 3/3] Downgrading to pylint 2.3 since 2.4 won't install on Python 3.4 --- lint.sh | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lint.sh b/lint.sh index fe695c293..0fd5058a3 100755 --- a/lint.sh +++ b/lint.sh @@ -31,7 +31,7 @@ function lintChangedFiles () { set -o errexit set -o nounset -SKIP_FOR_TESTS="redefined-outer-name,protected-access,missing-docstring,too-many-lines" +SKIP_FOR_TESTS="redefined-outer-name,protected-access,missing-docstring,too-many-lines,len-as-condition" SKIP_FOR_SNIPPETS="${SKIP_FOR_TESTS},reimported,unused-variable,unused-import,import-outside-toplevel" if [[ "$#" -eq 1 && "$1" = "all" ]] diff --git a/requirements.txt b/requirements.txt index a3c483e25..6d28b38ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -pylint == 2.4.4 +pylint == 2.3.1 pytest >= 3.6.0 pytest-cov >= 2.4.0 pytest-localserver >= 0.4.1