Skip to content

Add Python 3.10 support and bump version number #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the quotes intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, due to an issue with how the workflows are parsed: actions/setup-python#160

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