-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathapi_config.py
2262 lines (1858 loc) · 83.6 KB
/
api_config.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Library for generating an API configuration document for a ProtoRPC backend.
The protorpc.remote.Service is inspected and a JSON document describing
the API is returned.
class MyResponse(messages.Message):
bool_value = messages.BooleanField(1)
int32_value = messages.IntegerField(2)
class MyService(remote.Service):
@remote.method(message_types.VoidMessage, MyResponse)
def entries_get(self, request):
pass
api = ApiConfigGenerator().pretty_print_config_to_json(MyService)
"""
# pylint: disable=g-bad-name
# pylint: disable=g-statement-before-imports,g-import-not-at-top
from __future__ import absolute_import
import json
import logging
import re
from google.appengine.api import app_identity
import attr
import semver
from protorpc import util
from . import api_exceptions
from . import constants
from . import message_parser
from . import message_types
from . import messages
from . import remote
from . import resource_container
from . import types as endpoints_types
# originally in this module
from .types import Issuer, LimitDefinition, Namespace
from . import users_id_token
from . import util as endpoints_util
_logger = logging.getLogger(__name__)
package = 'google.appengine.endpoints'
__all__ = [
'ApiAuth',
'ApiConfigGenerator',
'ApiFrontEndLimitRule',
'ApiFrontEndLimits',
'EMAIL_SCOPE',
'Issuer',
'LimitDefinition',
'Namespace',
'api',
'method',
'AUTH_LEVEL',
'package',
]
EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
_EMAIL_SCOPE_DESCRIPTION = 'View your email address'
_EMAIL_SCOPE_OBJ = endpoints_types.OAuth2Scope(
scope=EMAIL_SCOPE, description=_EMAIL_SCOPE_DESCRIPTION)
_PATH_VARIABLE_PATTERN = r'{([a-zA-Z_][a-zA-Z_.\d]*)}'
_MULTICLASS_MISMATCH_ERROR_TEMPLATE = (
'Attempting to implement service %s, version %s, with multiple '
'classes that aren\'t compatible. See docstring for api() for '
'examples how to implement a multi-class API.')
_INVALID_NAMESPACE_ERROR_TEMPLATE = (
'Invalid namespace configuration. If a namespace is set, make sure to set '
'%s. package_path is optional.')
_VALID_PART_RE = re.compile('^{[^{}]+}$')
_VALID_LAST_PART_RE = re.compile('^{[^{}]+}(:)?(?(1)[^{}]+)$')
def _Enum(docstring, *names):
"""Utility to generate enum classes used by annotations.
Args:
docstring: Docstring for the generated enum class.
*names: Enum names.
Returns:
A class that contains enum names as attributes.
"""
enums = dict(zip(names, range(len(names))))
reverse = dict((value, key) for key, value in enums.iteritems())
enums['reverse_mapping'] = reverse
enums['__doc__'] = docstring
return type('Enum', (object,), enums)
_AUTH_LEVEL_DOCSTRING = """
Define the enums used by the auth_level annotation to specify frontend
authentication requirement.
Frontend authentication is handled by a Google API server prior to the
request reaching backends. An early return before hitting the backend can
happen if the request does not fulfil the requirement specified by the
auth_level.
Valid values of auth_level and their meanings are:
AUTH_LEVEL.REQUIRED: Valid authentication credentials are required. Backend
will be called only if authentication credentials are present and valid.
AUTH_LEVEL.OPTIONAL: Authentication is optional. If authentication credentials
are supplied they must be valid. Backend will be called if the request
contains valid authentication credentials or no authentication credentials.
AUTH_LEVEL.OPTIONAL_CONTINUE: Authentication is optional and will be attempted
if authentication credentials are supplied. Invalid authentication
credentials will be removed but the request can always reach backend.
AUTH_LEVEL.NONE: Frontend authentication will be skipped. If authentication is
desired, it will need to be performed by the backend.
"""
AUTH_LEVEL = _Enum(_AUTH_LEVEL_DOCSTRING, 'REQUIRED', 'OPTIONAL',
'OPTIONAL_CONTINUE', 'NONE')
_AUTH_LEVEL_WARNING = ("Due to a design error, auth_level has never actually been functional. "
"It will likely be removed and replaced by a functioning alternative "
"in a future version of the framework. Please stop using auth_level now.")
def _GetFieldAttributes(field):
"""Decomposes field into the needed arguments to pass to the constructor.
This can be used to create copies of the field or to compare if two fields
are "equal" (since __eq__ is not implemented on messages.Field).
Args:
field: A ProtoRPC message field (potentially to be copied).
Raises:
TypeError: If the field is not an instance of messages.Field.
Returns:
A pair of relevant arguments to be passed to the constructor for the field
type. The first element is a list of positional arguments for the
constructor and the second is a dictionary of keyword arguments.
"""
if not isinstance(field, messages.Field):
raise TypeError('Field %r to be copied not a ProtoRPC field.' % (field,))
positional_args = []
kwargs = {
'required': field.required,
'repeated': field.repeated,
'variant': field.variant,
'default': field._Field__default, # pylint: disable=protected-access
}
if isinstance(field, messages.MessageField):
# Message fields can't have a default
kwargs.pop('default')
if not isinstance(field, message_types.DateTimeField):
positional_args.insert(0, field.message_type)
elif isinstance(field, messages.EnumField):
positional_args.insert(0, field.type)
return positional_args, kwargs
def _CheckType(value, check_type, name, allow_none=True):
"""Check that the type of an object is acceptable.
Args:
value: The object whose type is to be checked.
check_type: The type that the object must be an instance of.
name: Name of the object, to be placed in any error messages.
allow_none: True if value can be None, false if not.
Raises:
TypeError: If value is not an acceptable type.
"""
if value is None and allow_none:
return
if not isinstance(value, check_type):
raise TypeError('%s type doesn\'t match %s.' % (name, check_type))
def _CheckEnum(value, check_type, name):
if value is None:
return
if value not in check_type.reverse_mapping:
raise TypeError('%s is not a valid value for %s' % (value, name))
def _CheckNamespace(namespace):
_CheckType(namespace, Namespace, 'namespace')
if namespace:
if not namespace.owner_domain:
raise api_exceptions.InvalidNamespaceException(
_INVALID_NAMESPACE_ERROR_TEMPLATE % 'owner_domain')
if not namespace.owner_name:
raise api_exceptions.InvalidNamespaceException(
_INVALID_NAMESPACE_ERROR_TEMPLATE % 'owner_name')
_CheckType(namespace.owner_domain, basestring, 'namespace.owner_domain')
_CheckType(namespace.owner_name, basestring, 'namespace.owner_name')
_CheckType(namespace.package_path, basestring, 'namespace.package_path')
def _CheckAudiences(audiences):
# Audiences can either be a list of audiences using the google_id_token
# or a dict mapping auth issuer name to the list of audiences.
if audiences is None or isinstance(audiences, dict):
return
else:
endpoints_util.check_list_type(audiences, basestring, 'audiences')
def _CheckLimitDefinitions(limit_definitions):
_CheckType(limit_definitions, list, 'limit_definitions')
if limit_definitions:
for ld in limit_definitions:
if not ld.metric_name:
raise api_exceptions.InvalidLimitDefinitionException(
"Metric name must be set in all limit definitions.")
if not ld.display_name:
raise api_exceptions.InvalidLimitDefinitionException(
"Display name must be set in all limit definitions.")
_CheckType(ld.metric_name, basestring, 'limit_definition.metric_name')
_CheckType(ld.display_name, basestring, 'limit_definition.display_name')
_CheckType(ld.default_limit, int, 'limit_definition.default_limit')
# pylint: disable=g-bad-name
class _ApiInfo(object):
"""Configurable attributes of an API.
A structured data object used to store API information associated with each
remote.Service-derived class that implements an API. This stores properties
that could be different for each class (such as the path or
collection/resource name), as well as properties common to all classes in
the API (such as API name and version).
"""
@util.positional(2)
def __init__(self, common_info, resource_name=None, path=None, audiences=None,
scopes=None, allowed_client_ids=None, auth_level=None,
api_key_required=None):
"""Constructor for _ApiInfo.
Args:
common_info: _ApiDecorator.__ApiCommonInfo, Information that's common for
all classes that implement an API.
resource_name: string, The collection that the annotated class will
implement in the API. (Default: None)
path: string, Base request path for all methods in this API.
(Default: None)
audiences: list of strings, Acceptable audiences for authentication.
(Default: None)
scopes: list of strings, Acceptable scopes for authentication.
(Default: None)
allowed_client_ids: list of strings, Acceptable client IDs for auth.
(Default: None)
auth_level: enum from AUTH_LEVEL, Frontend authentication level.
(Default: None)
api_key_required: bool, whether a key is required to call this API.
"""
_CheckType(resource_name, basestring, 'resource_name')
_CheckType(path, basestring, 'path')
endpoints_util.check_list_type(audiences, basestring, 'audiences')
endpoints_util.check_list_type(scopes, basestring, 'scopes')
endpoints_util.check_list_type(allowed_client_ids, basestring,
'allowed_client_ids')
_CheckEnum(auth_level, AUTH_LEVEL, 'auth_level')
_CheckType(api_key_required, bool, 'api_key_required')
self.__common_info = common_info
self.__resource_name = resource_name
self.__path = path
self.__audiences = audiences
self.__scopes = endpoints_types.OAuth2Scope.convert_list(scopes)
self.__allowed_client_ids = allowed_client_ids
self.__auth_level = auth_level
self.__api_key_required = api_key_required
def is_same_api(self, other):
"""Check if this implements the same API as another _ApiInfo instance."""
if not isinstance(other, _ApiInfo):
return False
# pylint: disable=protected-access
return self.__common_info is other.__common_info
@property
def name(self):
"""Name of the API."""
return self.__common_info.name
@property
def api_version(self):
"""Version of the API."""
return self.__common_info.api_version
@property
def path_version(self):
"""Version of the API for putting in the path."""
return self.__common_info.path_version
@property
def description(self):
"""Description of the API."""
return self.__common_info.description
@property
def hostname(self):
"""Hostname for the API."""
return self.__common_info.hostname
@property
def audiences(self):
"""List of audiences accepted for the API, overriding the defaults."""
if self.__audiences is not None:
return self.__audiences
return self.__common_info.audiences
@property
def scope_objs(self):
"""List of scopes (as OAuth2Scopes) accepted for the API, overriding the defaults."""
if self.__scopes is not None:
return self.__scopes
return self.__common_info.scope_objs
@property
def scopes(self):
"""List of scopes (as strings) accepted for the API, overriding the defaults."""
if self.scope_objs is not None:
return [_s.scope for _s in self.scope_objs]
@property
def allowed_client_ids(self):
"""List of client IDs accepted for the API, overriding the defaults."""
if self.__allowed_client_ids is not None:
return self.__allowed_client_ids
return self.__common_info.allowed_client_ids
@property
def issuers(self):
"""Dict mapping auth issuer names to auth issuers for the API."""
return self.__common_info.issuers
@property
def namespace(self):
"""Namespace for the API."""
return self.__common_info.namespace
@property
def auth_level(self):
"""Enum from AUTH_LEVEL specifying the frontend authentication level."""
if self.__auth_level is not None:
return self.__auth_level
return self.__common_info.auth_level
@property
def api_key_required(self):
"""bool specifying whether a key is required to call into this API."""
if self.__api_key_required is not None:
return self.__api_key_required
return self.__common_info.api_key_required
@property
def canonical_name(self):
"""Canonical name for the API."""
return self.__common_info.canonical_name
@property
def auth(self):
"""Authentication configuration information for this API."""
return self.__common_info.auth
@property
def owner_domain(self):
"""Domain of the owner of this API."""
return self.__common_info.owner_domain
@property
def owner_name(self):
"""Name of the owner of this API."""
return self.__common_info.owner_name
@property
def package_path(self):
"""Package this API belongs to, '/' delimited. Used by client libs."""
return self.__common_info.package_path
@property
def frontend_limits(self):
"""Optional query limits for unregistered developers."""
return self.__common_info.frontend_limits
@property
def title(self):
"""Human readable name of this API."""
return self.__common_info.title
@property
def documentation(self):
"""Link to the documentation for this version of the API."""
return self.__common_info.documentation
@property
def resource_name(self):
"""Resource name for the class this decorates."""
return self.__resource_name
@property
def path(self):
"""Base path prepended to any method paths in the class this decorates."""
return self.__path
@property
def base_path(self):
"""Base path for the entire API prepended before the path property."""
return self.__common_info.base_path
@property
def limit_definitions(self):
"""Rate limiting metric definitions for this API."""
return self.__common_info.limit_definitions
@property
def use_request_uri(self):
"""Match request paths based on the REQUEST_URI instead of PATH_INFO."""
return self.__common_info.use_request_uri
class _ApiDecorator(object):
"""Decorator for single- or multi-class APIs.
An instance of this class can be used directly as a decorator for a
single-class API. Or call the api_class() method to decorate a multi-class
API.
"""
@util.positional(3)
def __init__(self, name, version, description=None, hostname=None,
audiences=None, scopes=None, allowed_client_ids=None,
canonical_name=None, auth=None, owner_domain=None,
owner_name=None, package_path=None, frontend_limits=None,
title=None, documentation=None, auth_level=None, issuers=None,
namespace=None, api_key_required=None, base_path=None,
limit_definitions=None, use_request_uri=None):
"""Constructor for _ApiDecorator.
Args:
name: string, Name of the API.
version: string, Version of the API.
description: string, Short description of the API (Default: None)
hostname: string, Hostname of the API (Default: app engine default host)
audiences: list of strings, Acceptable audiences for authentication.
scopes: list of strings, Acceptable scopes for authentication.
allowed_client_ids: list of strings, Acceptable client IDs for auth.
canonical_name: string, the canonical name for the API, a more human
readable version of the name.
auth: ApiAuth instance, the authentication configuration information
for this API.
owner_domain: string, the domain of the person or company that owns
this API. Along with owner_name, this provides hints to properly
name client libraries for this API.
owner_name: string, the name of the owner of this API. Along with
owner_domain, this provides hints to properly name client libraries
for this API.
package_path: string, the "package" this API belongs to. This '/'
delimited value specifies logical groupings of APIs. This is used by
client libraries of this API.
frontend_limits: ApiFrontEndLimits, optional query limits for unregistered
developers.
title: string, the human readable title of your API. It is exposed in the
discovery service.
documentation: string, a URL where users can find documentation about this
version of the API. This will be surfaced in the API Explorer and GPE
plugin to allow users to learn about your service.
auth_level: enum from AUTH_LEVEL, Frontend authentication level.
issuers: dict, mapping auth issuer names to endpoints.Issuer objects.
namespace: endpoints.Namespace, the namespace for the API.
api_key_required: bool, whether a key is required to call this API.
base_path: string, the base path for all endpoints in this API.
limit_definitions: list of LimitDefinition tuples used in this API.
use_request_uri: if true, match requests against REQUEST_URI instead of PATH_INFO
"""
self.__common_info = self.__ApiCommonInfo(
name, version, description=description, hostname=hostname,
audiences=audiences, scopes=scopes,
allowed_client_ids=allowed_client_ids,
canonical_name=canonical_name, auth=auth, owner_domain=owner_domain,
owner_name=owner_name, package_path=package_path,
frontend_limits=frontend_limits, title=title,
documentation=documentation, auth_level=auth_level, issuers=issuers,
namespace=namespace, api_key_required=api_key_required,
base_path=base_path, limit_definitions=limit_definitions,
use_request_uri=use_request_uri)
self.__classes = []
class __ApiCommonInfo(object):
"""API information that's common among all classes that implement an API.
When a remote.Service-derived class implements part of an API, there is
some common information that remains constant across all such classes
that implement the same API. This includes things like name, version,
hostname, and so on. __ApiComminInfo stores that common information, and
a single __ApiCommonInfo instance is shared among all classes that
implement the same API, guaranteeing that they share the same common
information.
Some of these values can be overridden (such as audiences and scopes),
while some can't and remain the same for all classes that implement
the API (such as name and version).
"""
@util.positional(3)
def __init__(self, name, version, description=None, hostname=None,
audiences=None, scopes=None, allowed_client_ids=None,
canonical_name=None, auth=None, owner_domain=None,
owner_name=None, package_path=None, frontend_limits=None,
title=None, documentation=None, auth_level=None, issuers=None,
namespace=None, api_key_required=None, base_path=None,
limit_definitions=None, use_request_uri=None):
"""Constructor for _ApiCommonInfo.
Args:
name: string, Name of the API.
version: string, Version of the API.
description: string, Short description of the API (Default: None)
hostname: string, Hostname of the API (Default: app engine default host)
audiences: list of strings, Acceptable audiences for authentication.
scopes: list of strings, Acceptable scopes for authentication.
allowed_client_ids: list of strings, Acceptable client IDs for auth.
canonical_name: string, the canonical name for the API, a more human
readable version of the name.
auth: ApiAuth instance, the authentication configuration information
for this API.
owner_domain: string, the domain of the person or company that owns
this API. Along with owner_name, this provides hints to properly
name client libraries for this API.
owner_name: string, the name of the owner of this API. Along with
owner_domain, this provides hints to properly name client libraries
for this API.
package_path: string, the "package" this API belongs to. This '/'
delimited value specifies logical groupings of APIs. This is used by
client libraries of this API.
frontend_limits: ApiFrontEndLimits, optional query limits for
unregistered developers.
title: string, the human readable title of your API. It is exposed in
the discovery service.
documentation: string, a URL where users can find documentation about
this version of the API. This will be surfaced in the API Explorer and
GPE plugin to allow users to learn about your service.
auth_level: enum from AUTH_LEVEL, Frontend authentication level.
issuers: dict, mapping auth issuer names to endpoints.Issuer objects.
namespace: endpoints.Namespace, the namespace for the API.
api_key_required: bool, whether a key is required to call into this API.
base_path: string, the base path for all endpoints in this API.
limit_definitions: list of LimitDefinition tuples used in this API.
use_request_uri: if true, match requests against REQUEST_URI instead of PATH_INFO
"""
_CheckType(name, basestring, 'name', allow_none=False)
_CheckType(version, basestring, 'version', allow_none=False)
_CheckType(description, basestring, 'description')
_CheckType(hostname, basestring, 'hostname')
endpoints_util.check_list_type(scopes, (basestring, endpoints_types.OAuth2Scope), 'scopes')
endpoints_util.check_list_type(allowed_client_ids, basestring,
'allowed_client_ids')
_CheckType(canonical_name, basestring, 'canonical_name')
_CheckType(auth, ApiAuth, 'auth')
_CheckType(owner_domain, basestring, 'owner_domain')
_CheckType(owner_name, basestring, 'owner_name')
_CheckType(package_path, basestring, 'package_path')
_CheckType(frontend_limits, ApiFrontEndLimits, 'frontend_limits')
_CheckType(title, basestring, 'title')
_CheckType(documentation, basestring, 'documentation')
_CheckEnum(auth_level, AUTH_LEVEL, 'auth_level')
_CheckType(api_key_required, bool, 'api_key_required')
_CheckType(base_path, basestring, 'base_path')
_CheckType(issuers, dict, 'issuers')
if issuers:
for issuer_name, issuer_value in issuers.items():
_CheckType(issuer_name, basestring, 'issuer %s' % issuer_name)
_CheckType(issuer_value, Issuer, 'issuer value for %s' % issuer_name)
_CheckNamespace(namespace)
_CheckAudiences(audiences)
_CheckLimitDefinitions(limit_definitions)
_CheckType(use_request_uri, bool, 'use_request_uri')
if hostname is None:
hostname = app_identity.get_default_version_hostname()
if scopes is None:
scopes = [_EMAIL_SCOPE_OBJ]
else:
scopes = endpoints_types.OAuth2Scope.convert_list(scopes)
if allowed_client_ids is None:
allowed_client_ids = [constants.API_EXPLORER_CLIENT_ID]
if auth_level is None:
auth_level = AUTH_LEVEL.NONE
if api_key_required is None:
api_key_required = False
if base_path is None:
base_path = '/_ah/api/'
if use_request_uri is None:
use_request_uri = False
self.__name = name
self.__api_version = version
try:
parsed_version = semver.parse(version)
except ValueError:
self.__path_version = version
else:
self.__path_version = 'v{0}'.format(parsed_version['major'])
self.__description = description
self.__hostname = hostname
self.__audiences = audiences
self.__scopes = scopes
self.__allowed_client_ids = allowed_client_ids
self.__canonical_name = canonical_name
self.__auth = auth
self.__owner_domain = owner_domain
self.__owner_name = owner_name
self.__package_path = package_path
self.__frontend_limits = frontend_limits
self.__title = title
self.__documentation = documentation
self.__auth_level = auth_level
self.__issuers = issuers
self.__namespace = namespace
self.__api_key_required = api_key_required
self.__base_path = base_path
self.__limit_definitions = limit_definitions
self.__use_request_uri = use_request_uri
@property
def name(self):
"""Name of the API."""
return self.__name
@property
def api_version(self):
"""Version of the API."""
return self.__api_version
@property
def path_version(self):
"""Version of the API for putting in the path."""
return self.__path_version
@property
def description(self):
"""Description of the API."""
return self.__description
@property
def hostname(self):
"""Hostname for the API."""
return self.__hostname
@property
def audiences(self):
"""List of audiences accepted by default for the API."""
return self.__audiences
@property
def scope_objs(self):
"""List of scopes (as OAuth2Scopes) accepted by default for the API."""
return self.__scopes
@property
def scopes(self):
"""List of scopes (as strings) accepted by default for the API."""
if self.scope_objs is not None:
return [_s.scope for _s in self.scope_objs]
@property
def allowed_client_ids(self):
"""List of client IDs accepted by default for the API."""
return self.__allowed_client_ids
@property
def issuers(self):
"""List of auth issuers for the API."""
return self.__issuers
@property
def namespace(self):
"""Namespace of the API."""
return self.__namespace
@property
def auth_level(self):
"""Enum from AUTH_LEVEL specifying default frontend auth level."""
return self.__auth_level
@property
def canonical_name(self):
"""Canonical name for the API."""
return self.__canonical_name
@property
def auth(self):
"""Authentication configuration for this API."""
return self.__auth
@property
def api_key_required(self):
"""Whether a key is required to call into this API."""
return self.__api_key_required
@property
def owner_domain(self):
"""Domain of the owner of this API."""
return self.__owner_domain
@property
def owner_name(self):
"""Name of the owner of this API."""
return self.__owner_name
@property
def package_path(self):
"""Package this API belongs to, '/' delimited. Used by client libs."""
return self.__package_path
@property
def frontend_limits(self):
"""Optional query limits for unregistered developers."""
return self.__frontend_limits
@property
def title(self):
"""Human readable name of this API."""
return self.__title
@property
def documentation(self):
"""Link to the documentation for this version of the API."""
return self.__documentation
@property
def base_path(self):
"""The base path for all endpoints in this API."""
return self.__base_path
@property
def limit_definitions(self):
"""Rate limiting metric definitions for this API."""
return self.__limit_definitions
@property
def use_request_uri(self):
"""Match request paths based on the REQUEST_URI instead of PATH_INFO."""
return self.__use_request_uri
def __call__(self, service_class):
"""Decorator for ProtoRPC class that configures Google's API server.
Args:
service_class: remote.Service class, ProtoRPC service class being wrapped.
Returns:
Same class with API attributes assigned in api_info.
"""
return self.api_class()(service_class)
def api_class(self, resource_name=None, path=None, audiences=None,
scopes=None, allowed_client_ids=None, auth_level=None,
api_key_required=None):
"""Get a decorator for a class that implements an API.
This can be used for single-class or multi-class implementations. It's
used implicitly in simple single-class APIs that only use @api directly.
Args:
resource_name: string, Resource name for the class this decorates.
(Default: None)
path: string, Base path prepended to any method paths in the class this
decorates. (Default: None)
audiences: list of strings, Acceptable audiences for authentication.
(Default: None)
scopes: list of strings, Acceptable scopes for authentication.
(Default: None)
allowed_client_ids: list of strings, Acceptable client IDs for auth.
(Default: None)
auth_level: enum from AUTH_LEVEL, Frontend authentication level.
(Default: None)
api_key_required: bool, Whether a key is required to call into this API.
(Default: None)
Returns:
A decorator function to decorate a class that implements an API.
"""
if auth_level is not None:
_logger.warn(_AUTH_LEVEL_WARNING)
def apiserving_api_decorator(api_class):
"""Decorator for ProtoRPC class that configures Google's API server.
Args:
api_class: remote.Service class, ProtoRPC service class being wrapped.
Returns:
Same class with API attributes assigned in api_info.
"""
self.__classes.append(api_class)
api_class.api_info = _ApiInfo(
self.__common_info, resource_name=resource_name,
path=path, audiences=audiences, scopes=scopes,
allowed_client_ids=allowed_client_ids, auth_level=auth_level,
api_key_required=api_key_required)
return api_class
return apiserving_api_decorator
def get_api_classes(self):
"""Get the list of remote.Service classes that implement this API."""
return self.__classes
class ApiAuth(object):
"""Optional authorization configuration information for an API."""
def __init__(self, allow_cookie_auth=None, blocked_regions=None):
"""Constructor for ApiAuth, authentication information for an API.
Args:
allow_cookie_auth: boolean, whether cooking auth is allowed. By
default, API methods do not allow cookie authentication, and
require the use of OAuth2 or ID tokens. Setting this field to
True will allow cookies to be used to access the API, with
potentially dangerous results. Please be very cautious in enabling
this setting, and make sure to require appropriate XSRF tokens to
protect your API.
blocked_regions: list of Strings, a list of 2-letter ISO region codes
to block.
"""
_CheckType(allow_cookie_auth, bool, 'allow_cookie_auth')
endpoints_util.check_list_type(blocked_regions, basestring,
'blocked_regions')
self.__allow_cookie_auth = allow_cookie_auth
self.__blocked_regions = blocked_regions
@property
def allow_cookie_auth(self):
"""Whether cookie authentication is allowed for this API."""
return self.__allow_cookie_auth
@property
def blocked_regions(self):
"""List of 2-letter ISO region codes to block."""
return self.__blocked_regions
class ApiFrontEndLimitRule(object):
"""Custom rule to limit unregistered traffic."""
def __init__(self, match=None, qps=None, user_qps=None, daily=None,
analytics_id=None):
"""Constructor for ApiFrontEndLimitRule.
Args:
match: string, the matching rule that defines this traffic segment.
qps: int, the aggregate QPS for this segment.
user_qps: int, the per-end-user QPS for this segment.
daily: int, the aggregate daily maximum for this segment.
analytics_id: string, the project ID under which traffic for this segment
will be logged.
"""
_CheckType(match, basestring, 'match')
_CheckType(qps, int, 'qps')
_CheckType(user_qps, int, 'user_qps')
_CheckType(daily, int, 'daily')
_CheckType(analytics_id, basestring, 'analytics_id')
self.__match = match
self.__qps = qps
self.__user_qps = user_qps
self.__daily = daily
self.__analytics_id = analytics_id
@property
def match(self):
"""The matching rule that defines this traffic segment."""
return self.__match
@property
def qps(self):
"""The aggregate QPS for this segment."""
return self.__qps
@property
def user_qps(self):
"""The per-end-user QPS for this segment."""
return self.__user_qps
@property
def daily(self):
"""The aggregate daily maximum for this segment."""
return self.__daily
@property
def analytics_id(self):
"""Project ID under which traffic for this segment will be logged."""
return self.__analytics_id
class ApiFrontEndLimits(object):
"""Optional front end limit information for an API."""
def __init__(self, unregistered_user_qps=None, unregistered_qps=None,
unregistered_daily=None, rules=None):
"""Constructor for ApiFrontEndLimits, front end limit info for an API.
Args:
unregistered_user_qps: int, the per-end-user QPS. Users are identified
by their IP address. A value of 0 will block unregistered requests.
unregistered_qps: int, an aggregate QPS upper-bound for all unregistered
traffic. A value of 0 currently means unlimited, though it might change
in the future. To block unregistered requests, use unregistered_user_qps
or unregistered_daily instead.
unregistered_daily: int, an aggregate daily upper-bound for all
unregistered traffic. A value of 0 will block unregistered requests.
rules: A list or tuple of ApiFrontEndLimitRule instances: custom rules
used to apply limits to unregistered traffic.
"""
_CheckType(unregistered_user_qps, int, 'unregistered_user_qps')
_CheckType(unregistered_qps, int, 'unregistered_qps')
_CheckType(unregistered_daily, int, 'unregistered_daily')
endpoints_util.check_list_type(rules, ApiFrontEndLimitRule, 'rules')
self.__unregistered_user_qps = unregistered_user_qps
self.__unregistered_qps = unregistered_qps
self.__unregistered_daily = unregistered_daily
self.__rules = rules
@property
def unregistered_user_qps(self):
"""Per-end-user QPS limit."""
return self.__unregistered_user_qps
@property
def unregistered_qps(self):
"""Aggregate QPS upper-bound for all unregistered traffic."""
return self.__unregistered_qps
@property
def unregistered_daily(self):
"""Aggregate daily upper-bound for all unregistered traffic."""
return self.__unregistered_daily
@property
def rules(self):
"""Custom rules used to apply limits to unregistered traffic."""
return self.__rules
@util.positional(2)
def api(name, version, description=None, hostname=None, audiences=None,
scopes=None, allowed_client_ids=None, canonical_name=None,
auth=None, owner_domain=None, owner_name=None, package_path=None,
frontend_limits=None, title=None, documentation=None, auth_level=None,
issuers=None, namespace=None, api_key_required=None, base_path=None,
limit_definitions=None, use_request_uri=None):
"""Decorate a ProtoRPC Service class for use by the framework above.
This decorator can be used to specify an API name, version, description, and
hostname for your API.
Sample usage (python 2.7):
@endpoints.api(name='guestbook', version='v0.2',