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 45138bd7fab5292a0dc9250ce9f14d0d7d079d07 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Wed, 8 Jan 2020 13:32:13 -0800 Subject: [PATCH 3/3] Removed the dependency on the six library --- firebase_admin/__init__.py | 10 ++++------ firebase_admin/_auth_utils.py | 22 ++++++++++------------ firebase_admin/_messaging_encoder.py | 14 ++++++-------- firebase_admin/_token_gen.py | 13 ++++++------- firebase_admin/_user_mgt.py | 16 ++++++++-------- firebase_admin/_utils.py | 4 ++-- firebase_admin/credentials.py | 5 ++--- firebase_admin/db.py | 21 ++++++++++----------- firebase_admin/instance_id.py | 3 +-- firebase_admin/messaging.py | 9 ++++----- firebase_admin/project_management.py | 5 ++--- firebase_admin/storage.py | 4 +--- integration/test_auth.py | 16 ++++++++-------- integration/test_db.py | 7 +++---- requirements.txt | 1 - setup.py | 1 - tests/test_exceptions.py | 4 ++-- tests/test_messaging.py | 18 ++++++++---------- tests/test_sseclient.py | 4 ++-- tests/test_token_gen.py | 3 +-- tests/test_user_mgt.py | 4 ++-- tests/testutils.py | 4 ++-- 22 files changed, 84 insertions(+), 104 deletions(-) diff --git a/firebase_admin/__init__.py b/firebase_admin/__init__.py index eae68bd06..400396266 100644 --- a/firebase_admin/__init__.py +++ b/firebase_admin/__init__.py @@ -18,8 +18,6 @@ import os import threading -import six - from firebase_admin import credentials from firebase_admin.__about__ import __version__ @@ -126,7 +124,7 @@ def get_app(name=_DEFAULT_APP_NAME): ValueError: If the specified name is not a string, or if the specified app does not exist. """ - if not isinstance(name, six.string_types): + if not isinstance(name, str): raise ValueError('Illegal app name argument type: "{}". App name ' 'must be a string.'.format(type(name))) with _apps_lock: @@ -203,7 +201,7 @@ def __init__(self, name, credential, options): Raises: ValueError: If an argument is None or invalid. """ - if not name or not isinstance(name, six.string_types): + if not name or not isinstance(name, str): raise ValueError('Illegal Firebase app name "{0}" provided. App name must be a ' 'non-empty string.'.format(name)) self._name = name @@ -221,7 +219,7 @@ def __init__(self, name, credential, options): @classmethod def _validate_project_id(cls, project_id): - if project_id is not None and not isinstance(project_id, six.string_types): + if project_id is not None and not isinstance(project_id, str): raise ValueError( 'Invalid project ID: "{0}". project ID must be a string.'.format(project_id)) @@ -286,7 +284,7 @@ def _get_service(self, name, initializer): Raises: ValueError: If the provided name is invalid, or if the App is already deleted. """ - if not name or not isinstance(name, six.string_types): + if not name or not isinstance(name, str): raise ValueError( 'Illegal name argument: "{0}". Name must be a non-empty string.'.format(name)) with self._lock: diff --git a/firebase_admin/_auth_utils.py b/firebase_admin/_auth_utils.py index b54e7d480..2f7383c0b 100644 --- a/firebase_admin/_auth_utils.py +++ b/firebase_admin/_auth_utils.py @@ -16,9 +16,7 @@ import json import re - -import six -from six.moves import urllib +from urllib import parse from firebase_admin import exceptions from firebase_admin import _utils @@ -35,7 +33,7 @@ def validate_uid(uid, required=False): if uid is None and not required: return None - if not isinstance(uid, six.string_types) or not uid or len(uid) > 128: + if not isinstance(uid, str) or not uid or len(uid) > 128: raise ValueError( 'Invalid uid: "{0}". The uid must be a non-empty string with no more than 128 ' 'characters.'.format(uid)) @@ -44,7 +42,7 @@ def validate_uid(uid, required=False): def validate_email(email, required=False): if email is None and not required: return None - if not isinstance(email, six.string_types) or not email: + if not isinstance(email, str) or not email: raise ValueError( 'Invalid email: "{0}". Email must be a non-empty string.'.format(email)) parts = email.split('@') @@ -61,7 +59,7 @@ def validate_phone(phone, required=False): """ if phone is None and not required: return None - if not isinstance(phone, six.string_types) or not phone: + if not isinstance(phone, str) or not phone: raise ValueError('Invalid phone number: "{0}". Phone number must be a non-empty ' 'string.'.format(phone)) if not phone.startswith('+') or not re.search('[a-zA-Z0-9]', phone): @@ -72,7 +70,7 @@ def validate_phone(phone, required=False): def validate_password(password, required=False): if password is None and not required: return None - if not isinstance(password, six.string_types) or len(password) < 6: + if not isinstance(password, str) or len(password) < 6: raise ValueError( 'Invalid password string. Password must be a string at least 6 characters long.') return password @@ -80,14 +78,14 @@ def validate_password(password, required=False): def validate_bytes(value, label, required=False): if value is None and not required: return None - if not isinstance(value, six.binary_type) or not value: + if not isinstance(value, bytes) or not value: raise ValueError('{0} must be a non-empty byte sequence.'.format(label)) return value def validate_display_name(display_name, required=False): if display_name is None and not required: return None - if not isinstance(display_name, six.string_types) or not display_name: + if not isinstance(display_name, str) or not display_name: raise ValueError( 'Invalid display name: "{0}". Display name must be a non-empty ' 'string.'.format(display_name)) @@ -96,7 +94,7 @@ def validate_display_name(display_name, required=False): def validate_provider_id(provider_id, required=True): if provider_id is None and not required: return None - if not isinstance(provider_id, six.string_types) or not provider_id: + if not isinstance(provider_id, str) or not provider_id: raise ValueError( 'Invalid provider ID: "{0}". Provider ID must be a non-empty ' 'string.'.format(provider_id)) @@ -106,12 +104,12 @@ 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: + if not isinstance(photo_url, str) or not photo_url: raise ValueError( 'Invalid photo URL: "{0}". Photo URL must be a non-empty ' 'string.'.format(photo_url)) try: - parsed = urllib.parse.urlparse(photo_url) + parsed = parse.urlparse(photo_url) if not parsed.netloc: raise ValueError('Malformed photo URL: "{0}".'.format(photo_url)) return photo_url diff --git a/firebase_admin/_messaging_encoder.py b/firebase_admin/_messaging_encoder.py index aefaf3e2f..c4da53f0d 100644 --- a/firebase_admin/_messaging_encoder.py +++ b/firebase_admin/_messaging_encoder.py @@ -20,8 +20,6 @@ import numbers import re -import six - import firebase_admin._messaging_utils as _messaging_utils @@ -99,7 +97,7 @@ def check_string(cls, label, value, non_empty=False): """Checks if the given value is a string.""" if value is None: return None - if not isinstance(value, six.string_types): + if not isinstance(value, str): if non_empty: raise ValueError('{0} must be a non-empty string.'.format(label)) raise ValueError('{0} must be a string.'.format(label)) @@ -122,10 +120,10 @@ def check_string_dict(cls, label, value): return None if not isinstance(value, dict): raise ValueError('{0} must be a dictionary.'.format(label)) - non_str = [k for k in value if not isinstance(k, six.string_types)] + non_str = [k for k in value if not isinstance(k, str)] if non_str: raise ValueError('{0} must not contain non-string keys.'.format(label)) - non_str = [v for v in value.values() if not isinstance(v, six.string_types)] + non_str = [v for v in value.values() if not isinstance(v, str)] if non_str: raise ValueError('{0} must not contain non-string values.'.format(label)) return value @@ -137,7 +135,7 @@ def check_string_list(cls, label, value): return None if not isinstance(value, list): raise ValueError('{0} must be a list of strings.'.format(label)) - non_str = [k for k in value if not isinstance(k, six.string_types)] + non_str = [k for k in value if not isinstance(k, str)] if non_str: raise ValueError('{0} must not contain non-string values.'.format(label)) return value @@ -570,7 +568,7 @@ def encode_aps_sound(cls, sound): """Encodes an APNs sound configuration into JSON.""" if sound is None: return None - if sound and isinstance(sound, six.string_types): + if sound and isinstance(sound, str): return sound if not isinstance(sound, _messaging_utils.CriticalSound): raise ValueError( @@ -593,7 +591,7 @@ def encode_aps_alert(cls, alert): """Encodes an ``ApsAlert`` instance into JSON.""" if alert is None: return None - if isinstance(alert, six.string_types): + if isinstance(alert, str): return alert if not isinstance(alert, _messaging_utils.ApsAlert): raise ValueError('Aps.alert must be a string or an instance of ApsAlert class.') diff --git a/firebase_admin/_token_gen.py b/firebase_admin/_token_gen.py index 471630cca..4234bfa7b 100644 --- a/firebase_admin/_token_gen.py +++ b/firebase_admin/_token_gen.py @@ -19,7 +19,6 @@ import cachecontrol import requests -import six from google.auth import credentials from google.auth import iam from google.auth import jwt @@ -149,7 +148,7 @@ def create_custom_token(self, uid, developer_claims=None): ', '.join(disallowed_keys))) raise ValueError(error_message) - if not uid or not isinstance(uid, six.string_types) or len(uid) > 128: + if not uid or not isinstance(uid, str) or len(uid) > 128: raise ValueError('uid must be a string between 1 and 128 characters.') signing_provider = self.signing_provider @@ -174,8 +173,8 @@ def create_custom_token(self, uid, developer_claims=None): def create_session_cookie(self, id_token, expires_in): """Creates a session cookie from the provided ID token.""" - id_token = id_token.decode('utf-8') if isinstance(id_token, six.binary_type) else id_token - if not isinstance(id_token, six.text_type) or not id_token: + id_token = id_token.decode('utf-8') if isinstance(id_token, bytes) else id_token + if not isinstance(id_token, str) or not id_token: raise ValueError( 'Illegal ID token provided: {0}. ID token must be a non-empty ' 'string.'.format(id_token)) @@ -256,8 +255,8 @@ def __init__(self, **kwargs): def verify(self, token, request): """Verifies the signature and data for the provided JWT.""" - token = token.encode('utf-8') if isinstance(token, six.text_type) else token - if not isinstance(token, six.binary_type) or not token: + token = token.encode('utf-8') if isinstance(token, str) else token + if not isinstance(token, bytes) or not token: raise ValueError( 'Illegal {0} provided: {1}. {0} must be a non-empty ' 'string.'.format(self.short_name, token)) @@ -308,7 +307,7 @@ def verify(self, token, request): 'Firebase {0} has incorrect "iss" (issuer) claim. Expected "{1}" but ' 'got "{2}". {3} {4}'.format(self.short_name, expected_issuer, issuer, project_id_match_msg, verify_id_token_msg)) - elif subject is None or not isinstance(subject, six.string_types): + elif subject is None or not isinstance(subject, str): error_message = ( 'Firebase {0} has no "sub" (subject) claim. ' '{1}'.format(self.short_name, verify_id_token_msg)) diff --git a/firebase_admin/_user_mgt.py b/firebase_admin/_user_mgt.py index 5b33abb39..533259e70 100644 --- a/firebase_admin/_user_mgt.py +++ b/firebase_admin/_user_mgt.py @@ -16,9 +16,9 @@ import base64 import json +from urllib import parse + import requests -import six -from six.moves import urllib from firebase_admin import _auth_utils from firebase_admin import _user_import @@ -397,7 +397,7 @@ def encode_action_code_settings(settings): raise ValueError("Dynamic action links url is mandatory") try: - parsed = urllib.parse.urlparse(settings.url) + parsed = parse.urlparse(settings.url) if not parsed.netloc: raise ValueError('Malformed dynamic action links url: "{0}".'.format(settings.url)) parameters['continueUrl'] = settings.url @@ -413,14 +413,14 @@ def encode_action_code_settings(settings): # dynamic_link_domain if settings.dynamic_link_domain is not None: - if not isinstance(settings.dynamic_link_domain, six.string_types): + if not isinstance(settings.dynamic_link_domain, str): raise ValueError('Invalid value provided for dynamic_link_domain: {0}' .format(settings.dynamic_link_domain)) parameters['dynamicLinkDomain'] = settings.dynamic_link_domain # ios_bundle_id if settings.ios_bundle_id is not None: - if not isinstance(settings.ios_bundle_id, six.string_types): + if not isinstance(settings.ios_bundle_id, str): raise ValueError('Invalid value provided for ios_bundle_id: {0}' .format(settings.ios_bundle_id)) parameters['iosBundleId'] = settings.ios_bundle_id @@ -431,13 +431,13 @@ def encode_action_code_settings(settings): raise ValueError("Android package name is required when specifying other Android settings") if settings.android_package_name is not None: - if not isinstance(settings.android_package_name, six.string_types): + if not isinstance(settings.android_package_name, str): raise ValueError('Invalid value provided for android_package_name: {0}' .format(settings.android_package_name)) parameters['androidPackageName'] = settings.android_package_name if settings.android_minimum_version is not None: - if not isinstance(settings.android_minimum_version, six.string_types): + if not isinstance(settings.android_minimum_version, str): raise ValueError('Invalid value provided for android_minimum_version: {0}' .format(settings.android_minimum_version)) parameters['androidMinimumVersion'] = settings.android_minimum_version @@ -486,7 +486,7 @@ def get_user(self, **kwargs): def list_users(self, page_token=None, max_results=MAX_LIST_USERS_RESULTS): """Retrieves a batch of users.""" if page_token is not None: - if not isinstance(page_token, six.string_types) or not page_token: + if not isinstance(page_token, str) or not page_token: raise ValueError('Page token must be a non-empty string.') if not isinstance(max_results, int): raise ValueError('Max results must be an integer.') diff --git a/firebase_admin/_utils.py b/firebase_admin/_utils.py index 7ec1b8fb8..2c4cec868 100644 --- a/firebase_admin/_utils.py +++ b/firebase_admin/_utils.py @@ -14,13 +14,13 @@ """Internal utilities common to all modules.""" +import io import json import socket import googleapiclient import httplib2 import requests -import six import firebase_admin from firebase_admin import exceptions @@ -255,7 +255,7 @@ def handle_googleapiclient_error(error, message=None, code=None, http_response=N def _http_response_from_googleapiclient_error(error): """Creates a requests HTTP Response object from the given googleapiclient error.""" resp = requests.models.Response() - resp.raw = six.BytesIO(error.content) + resp.raw = io.BytesIO(error.content) resp.status_code = error.resp.status return resp diff --git a/firebase_admin/credentials.py b/firebase_admin/credentials.py index e930675bd..8f9c504f0 100644 --- a/firebase_admin/credentials.py +++ b/firebase_admin/credentials.py @@ -15,7 +15,6 @@ """Firebase credentials module.""" import collections import json -import six import google.auth from google.auth.transport import requests @@ -79,7 +78,7 @@ def __init__(self, cert): ValueError: If the specified certificate is invalid. """ super(Certificate, self).__init__() - if isinstance(cert, six.string_types): + if isinstance(cert, str): with open(cert) as json_file: json_data = json.load(json_file) elif isinstance(cert, dict): @@ -180,7 +179,7 @@ def __init__(self, refresh_token): ValueError: If the refresh token configuration is invalid. """ super(RefreshToken, self).__init__() - if isinstance(refresh_token, six.string_types): + if isinstance(refresh_token, str): with open(refresh_token) as json_file: json_data = json.load(json_file) elif isinstance(refresh_token, dict): diff --git a/firebase_admin/db.py b/firebase_admin/db.py index 2fb8b3a74..9092a955c 100644 --- a/firebase_admin/db.py +++ b/firebase_admin/db.py @@ -25,11 +25,10 @@ import os import sys import threading +from urllib import parse import google.auth import requests -import six -from six.moves import urllib import firebase_admin from firebase_admin import exceptions @@ -73,7 +72,7 @@ def reference(path='/', app=None, url=None): def _parse_path(path): """Parses a path string into a set of segments.""" - if not isinstance(path, six.string_types): + if not isinstance(path, str): raise ValueError('Invalid path: "{0}". Path must be a string.'.format(path)) if any(ch in path for ch in _INVALID_PATH_CHARACTERS): raise ValueError( @@ -185,7 +184,7 @@ def child(self, path): Raises: ValueError: If the child path is not a string, not well-formed or begins with '/'. """ - if not path or not isinstance(path, six.string_types): + if not path or not isinstance(path, str): raise ValueError( 'Invalid path argument: "{0}". Path must be a non-empty string.'.format(path)) if path.startswith('/'): @@ -239,7 +238,7 @@ def get_if_changed(self, etag): ValueError: If the ETag is not a string. FirebaseError: If an error occurs while communicating with the remote database server. """ - if not isinstance(etag, six.string_types): + if not isinstance(etag, str): raise ValueError('ETag must be a string.') resp = self._client.request('get', self._add_suffix(), headers={'if-none-match': etag}) @@ -285,7 +284,7 @@ def set_if_unchanged(self, expected_etag, value): FirebaseError: If an error occurs while communicating with the remote database server. """ # pylint: disable=missing-raises-doc - if not isinstance(expected_etag, six.string_types): + if not isinstance(expected_etag, str): raise ValueError('Expected ETag must be a string.') if value is None: raise ValueError('Value must not be none.') @@ -488,7 +487,7 @@ class Query: def __init__(self, **kwargs): order_by = kwargs.pop('order_by') - if not order_by or not isinstance(order_by, six.string_types): + if not order_by or not isinstance(order_by, str): raise ValueError('order_by field must be a non-empty string') if order_by not in _RESERVED_FILTERS: if order_by.startswith('/'): @@ -704,7 +703,7 @@ def _get_index_type(cls, index): return cls._type_bool_true if isinstance(index, (int, float)): return cls._type_numeric - if isinstance(index, six.string_types): + if isinstance(index, str): return cls._type_string return cls._type_object @@ -825,11 +824,11 @@ def _parse_db_url(cls, url, emulator_host=None): base URL will use emulator_host instead. emulator_host is ignored if url is already an emulator URL. """ - if not url or not isinstance(url, six.string_types): + if not url or not isinstance(url, str): raise ValueError( 'Invalid database URL: "{0}". Database URL must be a non-empty ' 'URL string.'.format(url)) - parsed_url = urllib.parse.urlparse(url) + parsed_url = parse.urlparse(url) if parsed_url.netloc.endswith('.firebaseio.com'): return cls._parse_production_url(parsed_url, emulator_host) @@ -857,7 +856,7 @@ def _parse_production_url(cls, parsed_url, emulator_host): @classmethod def _parse_emulator_url(cls, parsed_url): """Parses emulator URL like http://localhost:8080/?ns=foo-bar""" - query_ns = urllib.parse.parse_qs(parsed_url.query).get('ns') + query_ns = parse.parse_qs(parsed_url.query).get('ns') if parsed_url.scheme != 'http' or (not query_ns or len(query_ns) != 1 or not query_ns[0]): raise ValueError( 'Invalid database URL: "{0}". Database URL must be a valid URL to a ' diff --git a/firebase_admin/instance_id.py b/firebase_admin/instance_id.py index f90d058cc..604158d9c 100644 --- a/firebase_admin/instance_id.py +++ b/firebase_admin/instance_id.py @@ -18,7 +18,6 @@ """ import requests -import six from firebase_admin import _http_client from firebase_admin import _utils @@ -80,7 +79,7 @@ def __init__(self, app): credential=app.credential.get_credential(), base_url=_IID_SERVICE_URL) def delete_instance_id(self, instance_id): - if not isinstance(instance_id, six.string_types) or not instance_id: + if not isinstance(instance_id, str) or not instance_id: raise ValueError('Instance ID must be a non-empty string.') path = 'project/{0}/instanceId/{1}'.format(self._project_id, instance_id) try: diff --git a/firebase_admin/messaging.py b/firebase_admin/messaging.py index 71366e5c4..9262751a1 100644 --- a/firebase_admin/messaging.py +++ b/firebase_admin/messaging.py @@ -15,12 +15,11 @@ """Firebase Cloud Messaging module.""" import json -import requests -import six import googleapiclient from googleapiclient import http from googleapiclient import _auth +import requests import firebase_admin from firebase_admin import _http_client @@ -395,15 +394,15 @@ def batch_callback(_, response, error): def make_topic_management_request(self, tokens, topic, operation): """Invokes the IID service for topic management functionality.""" - if isinstance(tokens, six.string_types): + if isinstance(tokens, str): tokens = [tokens] if not isinstance(tokens, list) or not tokens: raise ValueError('Tokens must be a string or a non-empty list of strings.') - invalid_str = [t for t in tokens if not isinstance(t, six.string_types) or not t] + invalid_str = [t for t in tokens if not isinstance(t, str) or not t] if invalid_str: raise ValueError('Tokens must be non-empty strings.') - if not isinstance(topic, six.string_types) or not topic: + if not isinstance(topic, str) or not topic: raise ValueError('Topic must be a non-empty string.') if not topic.startswith('/topics/'): topic = '/topics/{0}'.format(topic) diff --git a/firebase_admin/project_management.py b/firebase_admin/project_management.py index 076542bda..91aa1eebb 100644 --- a/firebase_admin/project_management.py +++ b/firebase_admin/project_management.py @@ -22,7 +22,6 @@ import time import requests -import six import firebase_admin from firebase_admin import exceptions @@ -117,13 +116,13 @@ def create_ios_app(bundle_id, display_name=None, app=None): def _check_is_string_or_none(obj, field_name): - if obj is None or isinstance(obj, six.string_types): + if obj is None or isinstance(obj, str): return obj raise ValueError('{0} must be a string.'.format(field_name)) def _check_is_nonempty_string(obj, field_name): - if isinstance(obj, six.string_types) and obj: + if isinstance(obj, str) and obj: return obj raise ValueError('{0} must be a non-empty string.'.format(field_name)) diff --git a/firebase_admin/storage.py b/firebase_admin/storage.py index a080b31ef..16f48e273 100644 --- a/firebase_admin/storage.py +++ b/firebase_admin/storage.py @@ -25,8 +25,6 @@ raise ImportError('Failed to import the Cloud Storage library for Python. Make sure ' 'to install the "google-cloud-storage" module.') -import six - from firebase_admin import _utils @@ -77,7 +75,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.') - if not bucket_name or not isinstance(bucket_name, six.string_types): + if not bucket_name or not isinstance(bucket_name, str): 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 c3759ce12..5d26dd9f1 100644 --- a/integration/test_auth.py +++ b/integration/test_auth.py @@ -17,13 +17,13 @@ import datetime import random import time +from urllib import parse import uuid import google.oauth2.credentials from google.auth import transport import pytest import requests -import six import firebase_admin from firebase_admin import auth @@ -82,8 +82,8 @@ def _sign_in_with_email_link(email, oob_code, api_key): return resp.json().get('idToken') def _extract_link_params(link): - query = six.moves.urllib.parse.urlparse(link).query - query_dict = dict(six.moves.urllib.parse.parse_qsl(query)) + query = parse.urlparse(link).query + query_dict = dict(parse.parse_qsl(query)) return query_dict def test_custom_token(api_key): @@ -427,7 +427,7 @@ def test_import_users_with_password(api_key): def test_password_reset(new_user_email_unverified, api_key): link = auth.generate_password_reset_link(new_user_email_unverified.email) - assert isinstance(link, six.string_types) + assert isinstance(link, str) query_dict = _extract_link_params(link) user_email = _reset_password(query_dict['oobCode'], 'newPassword', api_key) assert new_user_email_unverified.email == user_email @@ -436,7 +436,7 @@ def test_password_reset(new_user_email_unverified, api_key): def test_email_verification(new_user_email_unverified, api_key): link = auth.generate_email_verification_link(new_user_email_unverified.email) - assert isinstance(link, six.string_types) + assert isinstance(link, str) query_dict = _extract_link_params(link) user_email = _verify_email(query_dict['oobCode'], api_key) assert new_user_email_unverified.email == user_email @@ -446,7 +446,7 @@ def test_password_reset_with_settings(new_user_email_unverified, api_key): action_code_settings = auth.ActionCodeSettings(ACTION_LINK_CONTINUE_URL) link = auth.generate_password_reset_link(new_user_email_unverified.email, action_code_settings=action_code_settings) - assert isinstance(link, six.string_types) + assert isinstance(link, str) query_dict = _extract_link_params(link) assert query_dict['continueUrl'] == ACTION_LINK_CONTINUE_URL user_email = _reset_password(query_dict['oobCode'], 'newPassword', api_key) @@ -458,7 +458,7 @@ def test_email_verification_with_settings(new_user_email_unverified, api_key): action_code_settings = auth.ActionCodeSettings(ACTION_LINK_CONTINUE_URL) link = auth.generate_email_verification_link(new_user_email_unverified.email, action_code_settings=action_code_settings) - assert isinstance(link, six.string_types) + assert isinstance(link, str) query_dict = _extract_link_params(link) assert query_dict['continueUrl'] == ACTION_LINK_CONTINUE_URL user_email = _verify_email(query_dict['oobCode'], api_key) @@ -469,7 +469,7 @@ def test_email_sign_in_with_settings(new_user_email_unverified, api_key): action_code_settings = auth.ActionCodeSettings(ACTION_LINK_CONTINUE_URL) link = auth.generate_sign_in_with_email_link(new_user_email_unverified.email, action_code_settings=action_code_settings) - assert isinstance(link, six.string_types) + assert isinstance(link, str) query_dict = _extract_link_params(link) assert query_dict['continueUrl'] == ACTION_LINK_CONTINUE_URL oob_code = query_dict['oobCode'] diff --git a/integration/test_db.py b/integration/test_db.py index abd02660f..7a73ea3ad 100644 --- a/integration/test_db.py +++ b/integration/test_db.py @@ -18,7 +18,6 @@ import os import pytest -import six import firebase_admin from firebase_admin import db @@ -113,7 +112,7 @@ def test_get_value_and_etag(self, testref, testdata): value, etag = testref.get(etag=True) assert isinstance(value, dict) assert testdata == value - assert isinstance(etag, six.string_types) + assert isinstance(etag, str) def test_get_shallow(self, testref): value = testref.get(shallow=True) @@ -124,7 +123,7 @@ def test_get_if_changed(self, testref, testdata): success, data, etag = testref.get_if_changed('wrong_etag') assert success is True assert data == testdata - assert isinstance(etag, six.string_types) + assert isinstance(etag, str) assert testref.get_if_changed(etag) == (False, None, None) def test_get_child_value(self, testref, testdata): @@ -211,7 +210,7 @@ def test_set_if_unchanged(self, testref): success, data, etag = edward.set_if_unchanged('invalid-etag', update_data) assert success is False assert data == push_data - assert isinstance(etag, six.string_types) + assert isinstance(etag, str) success, data, new_etag = edward.set_if_unchanged(etag, update_data) assert success is True diff --git a/requirements.txt b/requirements.txt index a3c483e25..ef36b5a27 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,3 @@ google-api-core[grpc] >= 1.14.0, < 2.0.0dev; platform.python_implementation != ' google-api-python-client >= 1.7.8 google-cloud-firestore >= 1.4.0; platform.python_implementation != 'PyPy' google-cloud-storage >= 1.18.0 -six >= 1.6.1 diff --git a/setup.py b/setup.py index b492ec922..43da5eb85 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,6 @@ 'google-api-python-client >= 1.7.8', 'google-cloud-firestore>=1.4.0; platform.python_implementation != "PyPy"', 'google-cloud-storage>=1.18.0', - 'six>=1.6.1' ] setup( diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 3df7ec2e3..96072d91b 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import io import json import socket @@ -19,7 +20,6 @@ import pytest import requests from requests import models -import six from googleapiclient import errors from firebase_admin import exceptions @@ -174,7 +174,7 @@ def _create_response(self, status=500, payload=None): resp = models.Response() resp.status_code = status if payload: - resp.raw = six.BytesIO(payload.encode()) + resp.raw = io.BytesIO(payload.encode()) exc = requests.exceptions.RequestException('Test error', response=resp) return resp, exc diff --git a/tests/test_messaging.py b/tests/test_messaging.py index 36f5943be..33c99445b 100644 --- a/tests/test_messaging.py +++ b/tests/test_messaging.py @@ -17,10 +17,8 @@ import json import numbers -import pytest -import six - from googleapiclient.http import HttpMockSequence +import pytest import firebase_admin from firebase_admin import exceptions @@ -288,7 +286,7 @@ def test_invalid_priority(self, data): with pytest.raises(ValueError) as excinfo: check_encoding(messaging.Message( topic='topic', android=messaging.AndroidConfig(priority=data))) - if isinstance(data, six.string_types): + if isinstance(data, str): assert str(excinfo.value) == 'AndroidConfig.priority must be "high" or "normal".' else: assert str(excinfo.value) == 'AndroidConfig.priority must be a non-empty string.' @@ -405,7 +403,7 @@ def test_invalid_icon(self, data): def test_invalid_color(self, data): notification = messaging.AndroidNotification(color=data) excinfo = self._check_notification(notification) - if isinstance(data, six.string_types): + if isinstance(data, str): assert str(excinfo.value) == 'AndroidNotification.color must be in the form #RRGGBB.' else: assert str(excinfo.value) == 'AndroidNotification.color must be a non-empty string.' @@ -491,7 +489,7 @@ def test_invalid_event_timestamp(self, timestamp): def test_invalid_priority(self, priority): notification = messaging.AndroidNotification(priority=priority) excinfo = self._check_notification(notification) - if isinstance(priority, six.string_types): + if isinstance(priority, str): if not priority: expected = 'AndroidNotification.priority must be a non-empty string.' else: @@ -505,7 +503,7 @@ def test_invalid_priority(self, priority): def test_invalid_visibility(self, visibility): notification = messaging.AndroidNotification(visibility=visibility) excinfo = self._check_notification(notification) - if isinstance(visibility, six.string_types): + if isinstance(visibility, str): if not visibility: expected = 'AndroidNotification.visibility must be a non-empty string.' else: @@ -679,7 +677,7 @@ def test_invalid_color(self, data): notification = messaging.LightSettings(color=data, light_on_duration_millis=300, light_off_duration_millis=200) excinfo = self._check_light_settings(notification) - if isinstance(data, six.string_types): + if isinstance(data, str): assert str(excinfo.value) == ('LightSettings.color must be in the form #RRGGBB or ' '#RRGGBBAA.') else: @@ -853,7 +851,7 @@ def test_invalid_badge(self, data): def test_invalid_direction(self, data): notification = messaging.WebpushNotification(direction=data) excinfo = self._check_notification(notification) - if isinstance(data, six.string_types): + if isinstance(data, str): assert str(excinfo.value) == ('WebpushNotification.direction must be "auto", ' '"ltr" or "rtl".') else: @@ -2195,7 +2193,7 @@ def _get_url(self, path): @pytest.mark.parametrize('tokens', [None, '', list(), dict(), tuple()]) def test_invalid_tokens(self, tokens): expected = 'Tokens must be a string or a non-empty list of strings.' - if isinstance(tokens, six.string_types): + if isinstance(tokens, str): expected = 'Tokens must be non-empty strings.' with pytest.raises(ValueError) as excinfo: diff --git a/tests/test_sseclient.py b/tests/test_sseclient.py index 881ecc6b9..70edcf0d0 100644 --- a/tests/test_sseclient.py +++ b/tests/test_sseclient.py @@ -13,10 +13,10 @@ # limitations under the License. """Tests for firebase_admin._sseclient.""" +import io import json import requests -import six from firebase_admin import _sseclient from tests import testutils @@ -31,7 +31,7 @@ def send(self, request, **kwargs): resp = super(MockSSEClientAdapter, self).send(request, **kwargs) resp.url = request.url resp.status_code = self.status - resp.raw = six.BytesIO(self.data.encode()) + resp.raw = io.BytesIO(self.data.encode()) resp.encoding = "utf-8" return resp diff --git a/tests/test_token_gen.py b/tests/test_token_gen.py index e92fd0059..439c1ba6e 100644 --- a/tests/test_token_gen.py +++ b/tests/test_token_gen.py @@ -26,7 +26,6 @@ import google.oauth2.id_token import pytest from pytest_localserver import plugin -import six import firebase_admin from firebase_admin import auth @@ -68,7 +67,7 @@ def _merge_jwt_claims(defaults, overrides): return defaults def _verify_custom_token(custom_token, expected_claims): - assert isinstance(custom_token, six.binary_type) + assert isinstance(custom_token, bytes) token = google.oauth2.id_token.verify_token( custom_token, testutils.MockRequest(200, MOCK_PUBLIC_CERTS), diff --git a/tests/test_user_mgt.py b/tests/test_user_mgt.py index f1572baf2..9b0b4ce11 100644 --- a/tests/test_user_mgt.py +++ b/tests/test_user_mgt.py @@ -17,9 +17,9 @@ import base64 import json import time +from urllib import parse import pytest -from six.moves import urllib import firebase_admin from firebase_admin import auth @@ -772,7 +772,7 @@ def _check_rpc_calls(self, recorder, expected=None): if expected is None: expected = {'maxResults' : '1000'} assert len(recorder) == 1 - request = dict(urllib.parse.parse_qsl(urllib.parse.urlsplit(recorder[0].url).query)) + request = dict(parse.parse_qsl(parse.urlsplit(recorder[0].url).query)) assert request == expected diff --git a/tests/testutils.py b/tests/testutils.py index 9c69663a0..d0663ead1 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -13,13 +13,13 @@ # limitations under the License. """Common utility classes and functions for testing.""" +import io import os from google.auth import credentials from google.auth import transport from requests import adapters from requests import models -import six import firebase_admin @@ -145,7 +145,7 @@ def send(self, request, **kwargs): # pylint: disable=arguments-differ resp = models.Response() resp.url = request.url resp.status_code = self._statuses[self._current_response] - resp.raw = six.BytesIO(self._responses[self._current_response].encode()) + resp.raw = io.BytesIO(self._responses[self._current_response].encode()) self._current_response = min(self._current_response + 1, len(self._responses) - 1) return resp