@@ -2664,6 +2664,168 @@ def __rollback_hook(self, rpc):
26642664 self .check_rpc_success (rpc )
26652665
26662666
2667+ _DATASTORE_APP_ID_ENV = 'DATASTORE_APP_ID'
2668+ _DATASTORE_PROJECT_ID_ENV = 'DATASTORE_PROJECT_ID'
2669+ _DATASTORE_ADDITIONAL_APP_IDS_ENV = 'DATASTORE_ADDITIONAL_APP_IDS'
2670+ _DATASTORE_USE_PROJECT_ID_AS_APP_ID_ENV = 'DATASTORE_USE_PROJECT_ID_AS_APP_ID'
2671+
2672+
2673+
2674+ def _CreateDefaultConnection (connection_fn , ** kwargs ):
2675+ """Creates a new connection to Datastore.
2676+
2677+ Uses environment variables to determine if the connection should be made
2678+ to Cloud Datastore v1beta3 or to Datastore's private App Engine API.
2679+ If DATASTORE_PROJECT_ID exists, connect to Datastore v1beta3. In this case,
2680+ either DATASTORE_APP_ID or DATASTORE_USE_PROJECT_ID_AS_APP_ID must be set to
2681+ indicate what the environment's application should be.
2682+
2683+ Args:
2684+ connection_fn: The function to use to create the connection.
2685+ **kwargs: Addition arguments to pass to the connection_fn.
2686+
2687+ Raises:
2688+ ValueError: If DATASTORE_PROJECT_ID is set but DATASTORE_APP_ID or
2689+ DATASTORE_USE_PROJECT_ID_AS_APP_ID is not. If DATASTORE_APP_ID doesn't
2690+ resolve to DATASTORE_PROJECT_ID. If DATASTORE_APP_ID doesn't match
2691+ an existing APPLICATION_ID.
2692+
2693+ Returns:
2694+ the connection object returned from connection_fn.
2695+ """
2696+ datastore_app_id = os .environ .get (_DATASTORE_APP_ID_ENV , None )
2697+ datastore_project_id = os .environ .get (_DATASTORE_PROJECT_ID_ENV , None )
2698+ if datastore_app_id or datastore_project_id :
2699+
2700+ app_id_override = bool (os .environ .get (
2701+ _DATASTORE_USE_PROJECT_ID_AS_APP_ID_ENV , False ))
2702+ if not datastore_app_id and not app_id_override :
2703+ raise ValueError ('Could not determine app id. To use project id (%s) '
2704+ 'instead, set %s=true. This will affect the '
2705+ 'serialized form of entities and should not be used '
2706+ 'if serialized entities will be shared between '
2707+ 'code running on App Engine and code running off '
2708+ 'App Engine. Alternatively, set %s=<app id>.'
2709+ % (datastore_project_id ,
2710+ _DATASTORE_USE_PROJECT_ID_AS_APP_ID_ENV ,
2711+ _DATASTORE_APP_ID_ENV ))
2712+ elif datastore_app_id :
2713+ if app_id_override :
2714+ raise ValueError ('App id was provided (%s) but %s was set to true. '
2715+ 'Please unset either %s or %s.' %
2716+ (datastore_app_id ,
2717+ _DATASTORE_USE_PROJECT_ID_AS_APP_ID_ENV ,
2718+ _DATASTORE_APP_ID_ENV ,
2719+ _DATASTORE_USE_PROJECT_ID_AS_APP_ID_ENV ))
2720+ elif datastore_project_id :
2721+
2722+ id_resolver = datastore_pbs .IdResolver ([datastore_app_id ])
2723+ if (datastore_project_id !=
2724+ id_resolver .resolve_project_id (datastore_app_id )):
2725+ raise ValueError ('App id "%s" does not match project id "%s".'
2726+ % (datastore_app_id , datastore_project_id ))
2727+
2728+ datastore_app_id = datastore_app_id or datastore_project_id
2729+ additional_app_str = os .environ .get (_DATASTORE_ADDITIONAL_APP_IDS_ENV , '' )
2730+ additional_apps = (app .strip () for app in additional_app_str .split (',' ))
2731+ return _CreateCloudDatastoreConnection (connection_fn ,
2732+ datastore_app_id ,
2733+ additional_apps ,
2734+ kwargs )
2735+ return connection_fn (** kwargs )
2736+
2737+
2738+
2739+ def _CreateCloudDatastoreConnection (connection_fn ,
2740+ app_id ,
2741+ external_app_ids ,
2742+ kwargs ):
2743+ """Creates a new context to connect to a remote Cloud Datastore instance.
2744+
2745+ This should only be used outside of Google App Engine.
2746+
2747+ Args:
2748+ connection_fn: A connection function which accepts both an _api_version
2749+ and an _id_resolver argument.
2750+ app_id: The application id to connect to. This differs from the project
2751+ id as it may have an additional prefix, e.g. "s~" or "e~".
2752+ external_app_ids: A list of apps that may be referenced by data in your
2753+ application. For example, if you are connected to s~my-app and store keys
2754+ for s~my-other-app, you should include s~my-other-app in the external_apps
2755+ list.
2756+ kwargs: The additional kwargs to pass to the connection_fn.
2757+
2758+ Raises:
2759+ ValueError: if the app_id provided doesn't match the current environment's
2760+ APPLICATION_ID.
2761+
2762+ Returns:
2763+ An ndb.Context that can connect to a Remote Cloud Datastore. You can use
2764+ this context by passing it to ndb.set_context.
2765+ """
2766+
2767+
2768+ from google .appengine .datastore import cloud_datastore_v1_remote_stub
2769+
2770+ if not datastore_pbs ._CLOUD_DATASTORE_ENABLED :
2771+ raise datastore_errors .BadArgumentError (
2772+ datastore_pbs .MISSING_CLOUD_DATASTORE_MESSAGE )
2773+
2774+ current_app_id = os .environ .get ('APPLICATION_ID' , None )
2775+ if current_app_id and current_app_id != app_id :
2776+
2777+
2778+ raise ValueError ('Cannot create a Cloud Datastore context that connects '
2779+ 'to an application (%s) that differs from the application '
2780+ 'already connected to (%s).' % (app_id , current_app_id ))
2781+ os .environ ['APPLICATION_ID' ] = app_id
2782+
2783+ id_resolver = datastore_pbs .IdResolver ((app_id ,) + tuple (external_app_ids ))
2784+ project_id = id_resolver .resolve_project_id (app_id )
2785+ endpoint = googledatastore .helper .get_project_endpoint_from_env (project_id )
2786+ datastore = googledatastore .Datastore (
2787+ project_endpoint = endpoint ,
2788+ credentials = googledatastore .helper .get_credentials_from_env ())
2789+ kwargs ['_api_version' ] = _CLOUD_DATASTORE_V1
2790+ kwargs ['_id_resolver' ] = id_resolver
2791+ conn = connection_fn (** kwargs )
2792+
2793+
2794+
2795+ try :
2796+ stub = cloud_datastore_v1_remote_stub .CloudDatastoreV1RemoteStub (datastore )
2797+ apiproxy_stub_map .apiproxy .RegisterStub (_CLOUD_DATASTORE_V1 ,
2798+ stub )
2799+ except :
2800+ pass
2801+
2802+
2803+
2804+ try :
2805+ apiproxy_stub_map .apiproxy .RegisterStub ('memcache' , _ThrowingStub ())
2806+ except :
2807+ pass
2808+ try :
2809+ apiproxy_stub_map .apiproxy .RegisterStub ('taskqueue' , _ThrowingStub ())
2810+ except :
2811+ pass
2812+
2813+ return conn
2814+
2815+
2816+ class _ThrowingStub (object ):
2817+ """A Stub implementation which always throws a NotImplementedError."""
2818+
2819+
2820+ def MakeSyncCall (self , service , call , request , response ):
2821+ raise NotImplementedError ('In order to use %s.%s you must '
2822+ 'install the Remote API.' % (service , call ))
2823+
2824+
2825+ def CreateRPC (self ):
2826+ return apiproxy_rpc .RPC (stub = self )
2827+
2828+
26672829
26682830
26692831
0 commit comments