1+ import os
2+ import types
13from typing import Any , Dict , List , Set
24
35from apiron import Endpoint
@@ -8,24 +10,70 @@ class ServiceMeta(type):
810 def required_headers (cls ) -> Dict [str , str ]:
911 return cls ().required_headers
1012
11- @property
12- def endpoints (cls ) -> Set [Endpoint ]:
13- return {attr for attr_name , attr in cls .__dict__ .items () if isinstance (attr , Endpoint )}
13+ @classmethod
14+ def _instantiated_services (cls ) -> bool :
15+ setting_variable = "APIRON_INSTANTIATED_SERVICES"
16+ false_values = ["0" , "false" ]
17+ true_values = ["1" , "true" ]
18+ environment_setting = os .getenv (setting_variable , "false" ).lower ()
19+ if environment_setting in false_values :
20+ return False
21+ elif environment_setting in true_values :
22+ return True
23+
24+ setting_values = false_values + true_values
25+ raise ValueError (
26+ f'Invalid { setting_variable } , "{ environment_setting } "\n ' ,
27+ f"{ setting_variable } must be one of { setting_values } \n " ,
28+ )
1429
1530 def __str__ (cls ) -> str :
1631 return str (cls ())
1732
1833 def __repr__ (cls ) -> str :
1934 return repr (cls ())
2035
36+ def __new__ (cls , name , bases , namespace , ** kwargs ):
37+ klass = super ().__new__ (cls , name , bases , namespace , ** kwargs )
38+
39+ # Behave as a normal class if instantiated services are enabled or if
40+ # this is an apiron base class.
41+ if cls ._instantiated_services () or klass .__module__ .split ("." )[:2 ] == ["apiron" , "service" ]:
42+ return klass
43+
44+ # Singleton class.
45+ if not hasattr (klass , "_instance" ):
46+ klass ._instance = klass ()
47+
48+ # Mask declared Endpoints with bound instance methods. (singleton)
49+ for k , v in namespace .items ():
50+ if isinstance (v , Endpoint ):
51+ setattr (klass , k , types .MethodType (v , klass ._instance ))
52+
53+ return klass ._instance
54+
2155
2256class ServiceBase (metaclass = ServiceMeta ):
2357 required_headers : Dict [str , Any ] = {}
2458 auth = ()
2559 proxies : Dict [str , str ] = {}
2660
27- @classmethod
28- def get_hosts (cls ) -> List [str ]:
61+ def __setattr__ (self , name , value ):
62+ """Transform assigned Endpoints into bound instance methods."""
63+ if isinstance (value , Endpoint ):
64+ value = types .MethodType (value , self )
65+ super ().__setattr__ (name , value )
66+
67+ @property
68+ def endpoints (self ) -> Set [Endpoint ]:
69+ endpoints = set ()
70+ for attr in self .__dict__ .values ():
71+ func = getattr (attr , "__func__" , None )
72+ if isinstance (func , Endpoint ):
73+ endpoints .add (func )
74+ return endpoints
75+
76+ def get_hosts (self ) -> List [str ]:
2977 """
3078 The fully-qualified hostnames that correspond to this service.
3179 These are often determined by asking a load balancer or service discovery mechanism.
@@ -35,7 +83,7 @@ def get_hosts(cls) -> List[str]:
3583 :rtype:
3684 list
3785 """
38- return []
86+ return [self . domain ]
3987
4088
4189class Service (ServiceBase ):
@@ -47,21 +95,21 @@ class Service(ServiceBase):
4795
4896 domain : str
4997
50- @classmethod
51- def get_hosts (cls ) -> List [str ]:
52- """
53- The fully-qualified hostnames that correspond to this service.
54- These are often determined by asking a load balancer or service discovery mechanism.
98+ @property
99+ def domain (self ):
100+ return self ._domain if self ._domain else self .__class__ .domain
55101
56- :return:
57- The hostname strings corresponding to this service
58- :rtype:
59- list
60- """
61- return [cls .domain ]
102+ def __init__ (self , domain = None , ** kwargs ):
103+ self ._domain = domain
104+ self ._kwargs = kwargs
105+
106+ # Mask declared Endpoints with bound instance methods. (instantiated)
107+ for name , attr in self .__class__ .__dict__ .items ():
108+ if isinstance (attr , Endpoint ):
109+ setattr (self , name , types .MethodType (attr , self ))
62110
63111 def __str__ (self ) -> str :
64- return self .__class__ . domain
112+ return self .domain
65113
66114 def __repr__ (self ) -> str :
67- return f"{ self .__class__ .__name__ } (domain={ self .__class__ . domain } )"
115+ return f"{ self .__class__ .__name__ } (domain={ self .domain } )"
0 commit comments