Skip to content

Commit

Permalink
Add Python 3.10 support and bump version number (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
asriniva authored Nov 15, 2021
1 parent 9c20e99 commit a11e8b7
Show file tree
Hide file tree
Showing 16 changed files with 132 additions and 126 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python: [3.7, 3.8, 3.9]
python: [3.7, 3.8, 3.9, '3.10']
steps:
- name: Checkout
uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ We are working to support more App Engine bundled service APIs for Python 3. To

In your `requirements.txt` file, add the following:

`appengine-python-standard>=0.2.4`
`appengine-python-standard>=0.3.0`

In your app's `app.yaml`, add the following:

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="appengine-python-standard",
version="0.2.4",
version="0.3.0",
author="Google LLC",
description="Google App Engine services SDK for Python 3",
long_description=long_description,
Expand All @@ -23,7 +23,7 @@
"protobuf>=3.19.0",
"pytz>=2021.1",
"requests>=2.25.1",
"ruamel.yaml>=0.15,<0.16",
"ruamel.yaml>=0.17.7",
"six>=1.15.0",
"urllib3>=1.26.2,<2",
],
Expand Down
17 changes: 0 additions & 17 deletions src/google/appengine/api/blobstore/blobstore_stub.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,23 +192,6 @@ def storage(self):
"""
return self.__storage

def _GetEnviron(self, name):
"""Helper method ensures environment configured as expected.
Args:
name: Name of environment variable to get.
Returns:
Environment variable associated with name.
Raises:
ConfigurationError if required environment variable is not found.
"""
try:
return os.environ[name]
except KeyError:
raise ConfigurationError('%s is not set in environment.' % name)

def _CreateSession(self,
success_path,
user,
Expand Down
89 changes: 51 additions & 38 deletions src/google/appengine/api/oauth/oauth_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
- `OAuthServiceFailureError`: OAuthService exception
"""

import contextvars
import json
import os
import six
Expand All @@ -50,6 +51,18 @@



_OAUTH_AUTH_DOMAIN = contextvars.ContextVar('OAUTH_AUTH_DOMAIN')
_OAUTH_EMAIL = contextvars.ContextVar('OAUTH_EMAIL')
_OAUTH_USER_ID = contextvars.ContextVar('OAUTH_USER_ID')
_OAUTH_CLIENT_ID = contextvars.ContextVar('OAUTH_CLIENT_ID')
_OAUTH_IS_ADMIN = contextvars.ContextVar('OAUTH_IS_ADMIN')
_OAUTH_ERROR_CODE = contextvars.ContextVar('OAUTH_ERROR_CODE')
_OAUTH_ERROR_DETAIL = contextvars.ContextVar('OAUTH_ERROR_DETAIL')
_OAUTH_LAST_SCOPE = contextvars.ContextVar('OAUTH_LAST_SCOPE')
_OAUTH_AUTHORIZED_SCOPES = contextvars.ContextVar('OAUTH_AUTHORIZED_SCOPES')

_TESTBED_RESET_TOKENS = dict()


class Error(Exception):
"""Base error class for this module."""
Expand Down Expand Up @@ -117,7 +130,7 @@ def is_current_user_admin(_scope=None):
"""

_maybe_call_get_oauth_user(_scope)
return os.environ.get('OAUTH_IS_ADMIN', '0') == '1'
return _OAUTH_IS_ADMIN.get(None)


def get_oauth_consumer_key():
Expand Down Expand Up @@ -165,14 +178,14 @@ def get_authorized_scopes(scope):


def _maybe_call_get_oauth_user(scope):
"""Makes an GetOAuthUser RPC and stores the results in os.environ.
"""Makes an GetOAuthUser RPC and stores the results in context.
This method will only make the RPC if 'OAUTH_ERROR_CODE' has not already
been set or 'OAUTH_LAST_SCOPE' is different to str(_scopes).
Args:
scope: The custom OAuth scope or an iterable of scopes at least one of
which is accepted.
scope: The custom OAuth scope or an iterable of scopes at least one of which
is accepted.
"""

if not scope:
Expand All @@ -181,8 +194,8 @@ def _maybe_call_get_oauth_user(scope):
scope_str = scope
else:
scope_str = str(sorted(scope))
if ('OAUTH_ERROR_CODE' not in os.environ or
os.environ.get('OAUTH_LAST_SCOPE', None) != scope_str or
if (_OAUTH_ERROR_CODE.get(None) is None or
_OAUTH_LAST_SCOPE.get(None) != scope_str or
os.environ.get('TESTONLY_OAUTH_SKIP_CACHE')):
req = user_service_pb2.GetOAuthUserRequest()
if scope:
Expand All @@ -194,35 +207,39 @@ def _maybe_call_get_oauth_user(scope):
resp = user_service_pb2.GetOAuthUserResponse()
try:
apiproxy_stub_map.MakeSyncCall('user', 'GetOAuthUser', req, resp)
os.environ['OAUTH_EMAIL'] = resp.email
os.environ['OAUTH_AUTH_DOMAIN'] = resp.auth_domain
os.environ['OAUTH_USER_ID'] = resp.user_id
os.environ['OAUTH_CLIENT_ID'] = resp.client_id

os.environ['OAUTH_AUTHORIZED_SCOPES'] = json.dumps(list(resp.scopes))
if resp.is_admin:
os.environ['OAUTH_IS_ADMIN'] = '1'
else:
os.environ['OAUTH_IS_ADMIN'] = '0'
os.environ['OAUTH_ERROR_CODE'] = ''
token = _OAUTH_EMAIL.set(resp.email)
_TESTBED_RESET_TOKENS[_OAUTH_EMAIL] = token
token = _OAUTH_AUTH_DOMAIN.set(resp.auth_domain)
_TESTBED_RESET_TOKENS[_OAUTH_AUTH_DOMAIN] = token
token = _OAUTH_USER_ID.set(resp.user_id)
_TESTBED_RESET_TOKENS[_OAUTH_USER_ID] = token
token = _OAUTH_CLIENT_ID.set(resp.client_id)
_TESTBED_RESET_TOKENS[_OAUTH_CLIENT_ID] = token
token = _OAUTH_AUTHORIZED_SCOPES.set(json.dumps(list(resp.scopes)))
_TESTBED_RESET_TOKENS[_OAUTH_AUTHORIZED_SCOPES] = token
token = _OAUTH_IS_ADMIN.set(resp.is_admin)
_TESTBED_RESET_TOKENS[_OAUTH_IS_ADMIN] = token
token = _OAUTH_ERROR_CODE.set('')
_TESTBED_RESET_TOKENS[_OAUTH_ERROR_CODE] = token
except apiproxy_errors.ApplicationError as e:
os.environ['OAUTH_ERROR_CODE'] = str(e.application_error)
os.environ['OAUTH_ERROR_DETAIL'] = e.error_detail
os.environ['OAUTH_LAST_SCOPE'] = scope_str
token = _OAUTH_ERROR_CODE.set(str(e.application_error))
_TESTBED_RESET_TOKENS[_OAUTH_ERROR_CODE] = token
token = _OAUTH_ERROR_DETAIL.set(e.error_detail)
_TESTBED_RESET_TOKENS[_OAUTH_ERROR_DETAIL] = token
token = _OAUTH_LAST_SCOPE.set(scope_str)
_TESTBED_RESET_TOKENS[_OAUTH_LAST_SCOPE] = token
_maybe_raise_exception()


def _maybe_raise_exception():
"""Raises an error if one has been stored in os.environ.
"""Raises an error if one has been stored in context.
This method requires that 'OAUTH_ERROR_CODE' has already been set (an empty
string indicates that there is no actual error).
"""
assert 'OAUTH_ERROR_CODE' in os.environ
error = os.environ['OAUTH_ERROR_CODE']
error = _OAUTH_ERROR_CODE.get()
if error:
assert 'OAUTH_ERROR_DETAIL' in os.environ
error_detail = os.environ['OAUTH_ERROR_DETAIL']
error_detail = _OAUTH_ERROR_DETAIL.get()
if error == str(user_service_pb2.UserServiceError.NOT_ALLOWED):
raise NotAllowedError(error_detail)
elif error == str(user_service_pb2.UserServiceError.OAUTH_INVALID_REQUEST):
Expand All @@ -236,42 +253,38 @@ def _maybe_raise_exception():


def _get_user_from_environ():
"""Returns a User based on values stored in os.environ.
"""Returns a User based on values stored in context.
This method requires that 'OAUTH_EMAIL', 'OAUTH_AUTH_DOMAIN', and
'OAUTH_USER_ID' have already been set.
Returns:
User
"""
assert 'OAUTH_EMAIL' in os.environ
assert 'OAUTH_AUTH_DOMAIN' in os.environ
assert 'OAUTH_USER_ID' in os.environ
return users.User(email=os.environ['OAUTH_EMAIL'],
_auth_domain=os.environ['OAUTH_AUTH_DOMAIN'],
_user_id=os.environ['OAUTH_USER_ID'])
return users.User(
email=_OAUTH_EMAIL.get(),
_auth_domain=_OAUTH_AUTH_DOMAIN.get(),
_user_id=_OAUTH_USER_ID.get())


def _get_client_id_from_environ():
"""Returns Client ID based on values stored in os.environ.
"""Returns Client ID based on values stored in context.
This method requires that 'OAUTH_CLIENT_ID' has already been set.
Returns:
string: the value of Client ID.
"""
assert 'OAUTH_CLIENT_ID' in os.environ
return os.environ['OAUTH_CLIENT_ID']
return _OAUTH_CLIENT_ID.get()


def _get_authorized_scopes_from_environ():
"""Returns authorized scopes based on values stored in os.environ.
"""Returns authorized scopes based on values stored in context.
This method requires that 'OAUTH_AUTHORIZED_SCOPES' has already been set.
Returns:
list: the list of OAuth scopes.
"""
assert 'OAUTH_AUTHORIZED_SCOPES' in os.environ

return json.loads(os.environ['OAUTH_AUTHORIZED_SCOPES'])
return json.loads(_OAUTH_AUTHORIZED_SCOPES.get())
21 changes: 8 additions & 13 deletions src/google/appengine/api/request_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@

import collections
import logging
import os

from google.appengine.runtime import context
from six.moves import urllib


Expand Down Expand Up @@ -636,23 +636,18 @@ def get_request_url(self, request_id):
Returns:
The URL of the request as a string.
"""
try:
host = os.environ['HTTP_HOST']
except KeyError:
host = os.environ['SERVER_NAME']
port = os.environ['SERVER_PORT']
host = context.get('HTTP_HOST')
if not host:
host = context.get('SERVER_NAME')
port = context.get('SERVER_PORT')
if port != '80':
host += ':' + port
url = 'http://' + host
url += urllib.parse.quote(os.environ.get('PATH_INFO', '/'))
if os.environ.get('QUERY_STRING'):
url += '?' + os.environ['QUERY_STRING']
url += urllib.parse.quote(context.get('PATH_INFO', '/'))
if context.get('QUERY_STRING'):
url += '?' + context.get('QUERY_STRING')
return url

def get_request_environ(self, request_id):
"""Returns a dict containing the WSGI environ for the request."""
return os.environ

def get_module(self, request_id):
"""Returns the name of the module serving this request.
Expand Down
11 changes: 5 additions & 6 deletions src/google/appengine/api/urlfetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"""URL downloading API."""

import email
import os
import threading

import six
Expand All @@ -32,6 +31,7 @@
from google.appengine.api import urlfetch_service_pb2
from google.appengine.api.urlfetch_errors import *
from google.appengine.runtime import apiproxy_errors
from google.appengine.runtime import context



Expand Down Expand Up @@ -195,16 +195,15 @@ def _is_fetching_self(url, method):
Boolean indicating whether or not it seems that the app is trying to fetch
itself.
"""
if (method != GET or
"HTTP_HOST" not in os.environ or
"PATH_INFO" not in os.environ):
if (method != GET or context.get('HTTP_HOST', None) is None or
context.get('PATH_INFO', None) is None):
return False

_, host_port, path, _, _ = six.moves.urllib.parse.urlsplit(url)

if host_port == os.environ['HTTP_HOST']:
if host_port == context.get('HTTP_HOST'):

current_path = urllib.parse.unquote(os.environ['PATH_INFO'])
current_path = urllib.parse.unquote(context.get('PATH_INFO'))
desired_path = urllib.parse.unquote(path)

if (current_path == desired_path or
Expand Down
7 changes: 4 additions & 3 deletions src/google/appengine/api/user_service_stub.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@


import logging
import os
from six.moves import urllib
import six.moves.urllib.parse
from google.appengine.api import apiproxy_stub
from google.appengine.api.oauth import oauth_api
from google.appengine.api import user_service_pb2
from google.appengine.runtime import apiproxy_errors
from google.appengine.runtime.context import ctx_test_util

_DEFAULT_LOGIN_URL = 'https://www.google.com/accounts/Login?continue=%s'
_DEFAULT_LOGOUT_URL = 'https://www.google.com/accounts/Logout?continue=%s'
Expand Down Expand Up @@ -84,12 +85,12 @@ def __init__(self,
self._logout_url = logout_url
self.__scopes = None

self.SetOAuthUser(is_admin=(os.environ.get('OAUTH_IS_ADMIN', '0') == '1'))
self.SetOAuthUser(is_admin=oauth_api._OAUTH_IS_ADMIN.get(False))




os.environ['AUTH_DOMAIN'] = auth_domain
ctx_test_util.set_both('AUTH_DOMAIN', auth_domain)

def SetOAuthUser(self,
email=_OAUTH_EMAIL,
Expand Down
9 changes: 5 additions & 4 deletions src/google/appengine/api/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from google.appengine.api import apiproxy_stub_map
from google.appengine.api import user_service_pb2
from google.appengine.runtime import apiproxy_errors
from google.appengine.runtime import context



Expand Down Expand Up @@ -102,12 +103,12 @@ def __init__(self, email=None, _auth_domain=None,


if _auth_domain is None:
_auth_domain = os.environ.get('AUTH_DOMAIN')
_auth_domain = context.get('AUTH_DOMAIN')
assert _auth_domain

if email is None and federated_identity is None:
email = os.environ.get('USER_EMAIL', email)
_user_id = os.environ.get('USER_ID', _user_id)
email = context.get('USER_EMAIL', email)
_user_id = context.get('USER_ID', _user_id)
federated_identity = os.environ.get('FEDERATED_IDENTITY',
federated_identity)
federated_provider = os.environ.get('FEDERATED_PROVIDER',
Expand Down Expand Up @@ -345,7 +346,7 @@ def is_current_user_admin():
Returns:
`True` if the user is an administrator; all other user types return `False`.
"""
return (os.environ.get('USER_IS_ADMIN', '0')) == '1'
return (context.get('USER_IS_ADMIN', '0')) == '1'


IsCurrentUserAdmin = is_current_user_admin
2 changes: 1 addition & 1 deletion src/google/appengine/datastore/datastore_index_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def ProcessIndexNode(self, node):
self.errors.append(
'Value for ancestor should be true or false, not "%s"' % ancestor)
properties = []
property_nodes = [n for n in node.getchildren() if n.tag == 'property']
property_nodes = [n for n in node if n.tag == 'property']


has_geospatial = any(
Expand Down
Loading

0 comments on commit a11e8b7

Please sign in to comment.