def test_validate_payload_not_valid_schema(): raw_data = b'{"baz": "bar"}' schema = t.Dict({t.Key('foo'): t.Atom('bar')}) with pytest.raises(JsonValidaitonError) as ctx: validate_payload(raw_data, schema) error = json.loads(ctx.value.text) assert error['error'] == 'Invalid json payload'
def test_raise_error(self): other = lambda v: DataError('other error', code='other_error') fail_other = t.Atom('a') & other res = extract_error(fail_other, 'a') assert res == 'other error' ttt = t.And(other, t.Any) res = extract_error(ttt, 45) assert res == 'other error'
def test_validate_payload_not_json(): raw_data = b'foo=bar' schema = t.Dict({t.Key('foo'): t.Atom('bar')}) with pytest.raises(JsonValidaitonError) as ctx: validate_payload(raw_data, schema) error = json.loads(ctx.value.text) assert error['error'] == 'Payload is not json serialisable'
def construct(arg): ''' Shortcut syntax to define trafarets. - int, str, float and bool will return t.Int, t.String, t.Float and t.Bool - one element list will return t.List - tuple or list with several args will return t.Tuple - dict will return t.Dict. If key has '?' at the and it will be optional and '?' will be removed - any callable will be t.Call - otherwise it will be returned as is construct is recursive and will try construct all lists, tuples and dicts args ''' if isinstance(arg, t.Trafaret): return arg elif isinstance(arg, tuple) or (isinstance(arg, list) and len(arg) > 1): return t.Tuple(*(construct(a) for a in arg)) elif isinstance(arg, list): # if len(arg) == 1 return t.List(construct(arg[0])) elif isinstance(arg, dict): return t.Dict({ construct_key(key): construct(value) for key, value in arg.items() }) elif isinstance(arg, str): return t.Atom(arg) elif isinstance(arg, type): if arg is int: return t.Int() elif arg is float: return t.Float() elif arg is str: return t.String() elif arg is bool: return t.Bool() else: return t.Type(arg) elif callable(arg): return t.Call(arg) else: return arg
def test_atom(self): res = t.Atom('atom').check('atom') self.assertEqual(res, 'atom') err = extract_error(t.Atom('atom'), 'molecule') self.assertEqual(err, "value is not exactly 'atom'")
def test_and(self): indeed_int = t.Atom('123') & int assert indeed_int('123') == 123 # fixed 0.8.0 error
def test_atom(self): res = t.Atom('atom').check('atom') assert res == 'atom' err = extract_error(t.Atom('atom'), 'molecule') assert err == "value is not exactly 'atom'"
class UnifiClient(AbstractUnifiSession): ''' Unifi API client ''' def login(self, username=None, password=None): self.debug('------LOGIN------') # if logging in with same user/passwd, check for login cookie if hasattr(self, 'username') and \ hasattr(self, 'password') and \ (username is None or self.username == username) and \ (password is None or self.password == password) and \ self.logged_in: self.debug('logged in -> cookie exists') self.debug('------END LOGIN------') return True # user/passwd from params overwites known user/passwd self.username = username or self.username if hasattr( self, 'username') else username self.password = password or self.password if hasattr( self, 'password') else password if self.username is None or self.password is None: # if instance doesn't have username or password, cannot login raise UnifiLoginError('Missing login information') # login with clean slate session self.clean_session() # make login call r = self.session.post(self.endpoint('/api/login'), headers={'Referer': self.endpoint('/login')}, json={ 'username': self.username, 'password': self.password, }) # debug messages self.debug('LOGIN REQUEST') self.debug('url:', r.request.url) self.debug('body:', r.request.body.decode()) self.debug('headers:', r.request.headers) self.debug('LOGIN RESPONSE') self.debug('status:', r.status_code) self.debug('text:', r.text) self.debug('headers:', r.headers) self.debug('cookies:', r.cookies) self.debug('------END LOGIN------') return self.logged_in def logout(self): if not self.logged_in: return False self.post(self.endpoint('/logout')) self.clean_session() return True @requires_login @guard(models.authorize_guest_params) def authorize_guest(self, client_mac, minutes, site=None, ap_mac=None, up_speed=None, down_speed=None, MB_limit=None): ''' Authorize a client device ------------------------- returns True on success params: Name | required | description ----------------------------------------- client_mac | True | client mac address minutes | True | minutes (from now) until authorization expires site | False | site name to authorize guest, if not provided, `find_device` will be used (or `default` if ap_mac wasn't provided) ap_mac | False | access point the client is connected (faster auth) up_speed | False | upload speed limit in kbps down_speed | False | download speed limit in kbps MB_limit | False | data transfer limit in MB ''' data = { 'cmd': 'authorize-guest', 'mac': client_mac.lower(), 'minutes': minutes, } if up_speed is not None: data['up'] = up_speed if down_speed is not None: data['down'] = down_speed if ap_mac is not None: site = self.find_device(ap_mac) if site is None else site assert site is not None, 'No site provided and AP does not belong to any known site' data['ap_mac'] = ap_mac if MB_limit is not None: data['MB_limit'] = MB_limit self.debug('Posting authorize guest: %s' % data) r = self.post(self.endpoint('/api/s/%s/cmd/stamgr' % site), json=data) return self.process_response(r, boolean=True) @requires_login @guard(cmd=t.String, client_mac=models.MacAddress, site=models.SiteName) def _guest_cmd(self, cmd, client_mac, site): ''' Run most stamgr command ------------------------- returns True on success params: Name | required | description ----------------------------------------- cmd | True | command to run client_mac | True | client mac address site | True | site name to authorize guest ''' data = { 'cmd': cmd, 'mac': client_mac, } r = self.post(self.endpoint('/api/s/%s/cmd/stamgr' % site), json=data) return self.process_response(r, boolean=True) @guard(client_mac=models.MacAddress, site=models.SiteName) def unauthorize_guest(self, client_mac, site='default'): ''' Unauthorize a client device ------------------------- returns True on success params: Name | required | description ----------------------------------------- client_mac | True | client mac address site | False | site name to authorize guest, defaults to `default` ''' return self._guest_cmd('unauthorize-guest', client_mac, site) @guard(client_mac=models.MacAddress, site=models.SiteName) def reconnect_sta(self, client_mac, site='default'): ''' Reconnect a client device ------------------------- returns True on success params: Name | required | description ----------------------------------------- client_mac | True | client mac address site | True | site name to authorize guest, defaults to `default` ''' return self._guest_cmd('kick-sta', client_mac, site) @guard(client_mac=models.MacAddress, site=models.SiteName) def block_sta(self, client_mac, site='default'): ''' Block a client device ------------------------- returns True on success params: Name | required | description ----------------------------------------- client_mac | True | client mac address site | True | site name to authorize guest, defaults to `default` ''' return self._guest_cmd('block-sta', client_mac, site) @guard(client_mac=models.MacAddress, site=models.SiteName) def unblock_sta(self, client_mac, site='default'): ''' Unblock a client device ------------------------- returns True on success params: Name | required | description ----------------------------------------- client_mac | True | client mac address site | True | site name to authorize guest, defaults to `default` ''' return self._guest_cmd('unblock-sta', client_mac, site) @requires_login @guard(client_macs=t.List(models.MacAddress), site=models.SiteName) def forget_sta(self, client_macs, site='default'): data = { 'cmd': 'forget-sta', 'mac': client_macs, } r = self.post(self.endpoint('/api/s/%s/cmd/stamgr' % site), json=data) return self.process_response(r, boolean=True) @requires_login def list_sites(self): ''' List sites managed by controller ------------------------- returns an array of site info on success ''' r = self.get(self.endpoint('/api/self/sites')) return self.process_response(r) @requires_login @guard(site=models.SiteName, device_mac=t.Or(models.MacAddress, t.Atom(None))) def list_devices(self, site='default', device_mac=None): ''' List devices managed by controller on a given site ------------------------- returns an array of devices info on success params: Name | required | description ----------------------------------------- site | True | site name to authorize guest, defaults to `default` device_mac | False | device mac address ''' # r = self.get(self.endpoint('/api/s/%s/stat/device/%s' % (site, device_mac.lower().replace(':', '').replace('-', '') or ''))) r = self.get( self.endpoint('/api/s/%s/stat/device/%s' % (site, device_mac or ''))) return self.process_response(r) @guard(device_mac=models.MacAddress) def find_device(self, device_mac): ''' Find site name of device by mac ------------------------- returns site name where device is located or None if device is not managed by this controller params: Name | required | description ----------------------------------------- device_mac | True | device mac address # This is a costly function, use with caution ''' for s in self.list_sites(): if device_mac in [ models.MacAddress(d['mac']) for d in self.list_devices(site=s['name']) ]: return models.SiteName(s['name']) # skipping user management functions for now # Sessions? @requires_login @guard(models.list_sessions_params) def list_sessions(self, client_mac=None, client_type='all', start=None, end=None, site='default'): ''' Get all login sessions ------------------------- returns an array of login sessions for all clients on a site, or a single client params: Name | required | description ----------------------------------------- client_type | False | type of client, can be: ['all', 'guest', 'user'] client_mac | False | client mac to get sessions, if not provided, all sessions will be returned start | False | Unix timestamp in seconds or datetime, defaults to end - 7d end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site name to get sessions, defaults to `default` ''' end = end * 1000 if isinstance( end, int) else int(datetime.now().timestamp() * 1000) if end is None else int(end.timestamp() * 1000) start = start * 1000 if isinstance( start, int) else end - 7 * 24 * 60 * 60 * 1000 if start is None else int( start.timestamp() * 1000) assert 0 < start < end, 'start must be before end (and both positive)' data = { 'type': client_type, 'start': start, 'end': end, } if client_mac is not None: data['mac'] = client_mac r = self.get(self.endpoint('/api/s/%s/stat/session' % site), json=data) return self.process_response(r) @requires_login @guard(client_mac=models.MacAddress, limit=t.Int, site=models.SiteName) def list_sessions_latest(self, client_mac, limit=5, site='default'): ''' Get latest login sessions for a given client ------------------------- returns an array of login sessions for a single client on a site params: Name | required | description ----------------------------------------- client_mac | True | client mac to get sessions limit | False | latest `limit` sessions will be returned, defaults to 5 site | False | site name to get sessions, defaults to `default` ''' data = { 'mac': client_mac, '_limit': limit, '_sort': '-assoc_time', } r = self.get(self.endpoint('/api/s/%s/stat/session' % site), json=data) return self.process_response(r) @requires_login @guard(models._base_time_site_params) def list_authorizations(self, start=None, end=None, site='default'): ''' Get latest authorizations for a given site ------------------------- returns an array of login sessions for a single client on a site params: Name | required | description ----------------------------------------- start | False | Unix timestamp in seconds or datetime, defaults to end - 7d end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site name to get authorizations, defaults to `default` ''' end = end * 1000 if isinstance( end, int) else int(datetime.now().timestamp() * 1000) if end is None else int(end.timestamp() * 1000) start = start * 1000 if isinstance( start, int) else end - 7 * 24 * 60 * 60 * 1000 if start is None else int( start.timestamp() * 1000) assert 0 < start < end, 'start must be before end (and both positive)' data = { 'start': start, 'end': end, } r = self.get(self.endpoint('/api/s/%s/stat/authorization' % site), json=data) return self.process_response(r) @requires_login @guard(last_hours=t.Int, site=models.SiteName) def list_allusers(self, last_hours=365 * 24, site='default'): ''' Get all users ever connected to a given site ------------------------- returns an array of client devices params: Name | required | description ----------------------------------------- last_hours | False | last `last_hours` to get users from, defaults to 1y (365*24 hours) site | False | site name to get authorizations, defaults to `default` # the stats returned are not affected by `last_hours` parameter since they're all-time stats for the device ''' data = { 'type': 'all', 'conn': 'all', 'within': last_hours, } r = self.get(self.endpoint('/api/s/%s/stat/allusers' % site), json=data) return self.process_response(r) @requires_login @guard(last_hours=t.Int, site=models.SiteName) def list_guests(self, last_hours=365 * 24, site='default'): ''' Get all guests ever connected to a given site, only valid accesses ------------------------- returns an array of guest devices params: Name | required | description ----------------------------------------- last_hours | False | last `last_hours` to get guests from, defaults to 1y (365*24 hours) site | False | site name to get authorizations, defaults to `default` ''' data = { 'within': last_hours, } r = self.get(self.endpoint('/api/s/%s/stat/guest' % site), json=data) return self.process_response(r) @requires_login @guard(client_mac=t.Or(models.MacAddress, t.Atom(None)), site=models.SiteName) def list_online_clients(self, client_mac=None, site='default'): ''' Get online client devices on a given site ------------------------- returns an array of guest devices params: Name | required | description ----------------------------------------- client_mac | False | client mac to search, if not provided all clients will be returned site | False | site name to get authorizations, defaults to `default` ''' data = { 'within': last_hours, } r = self.get(self.endpoint('/api/s/%s/stat/sta/%s' % (site, client_mac or '')), json=data) return self.process_response(r) @requires_login @guard(client_mac=models.MacAddress, site=models.SiteName) def client_info(self, client_mac, site='default'): ''' Get client device information ------------------------- returns an object with client device information params: Name | required | description ----------------------------------------- client_mac | True | client mac to search site | False | site name to get authorizations, defaults to `default` ''' r = self.get( self.endpoint('/api/s/%s/stat/user/%s' % (site, client_mac))) return self.process_response(r) # Site stats @requires_login @guard(models.site_stats_params.merge(models._inner_stats_extras)) def _site_stats(self, gran, def_range, start=None, end=None, site='default'): ''' Get site stats ------------------------- returns an array of stats for the given site params: Name | required | description ----------------------------------------- gran | True | granularity of the stats, only permitted: 5minutes, hourly, daily def_range | True | default range for the stats, only used if start wasn't given start | False | Unix timestamp in seconds or datetime, defaults to `end - def_range` end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site name to get stats, defaults to `default` # support and restrictions apply from 'site_stat_*' functions ''' end = end * 1000 if isinstance( end, int) else int(datetime.now().timestamp() * 1000) if end is None else int(end.timestamp() * 1000) start = start * 1000 if isinstance( start, int) else end - def_range if start is None else int( start.timestamp() * 1000) assert 0 < start < end, 'start must be before end (and both positive)' data = { 'attrs': [ 'bytes', 'wan-tx_bytes', 'wan-rx_bytes', 'wlan_bytes', 'num_sta', 'lan-num_sta', 'wlan-num_sta', 'time' ], 'start': start, 'end': end, } r = self.get(self.endpoint('/api/s/%s/stat/report/%s.site' % (site, gran)), json=data) return self.process_response(r) # site stats: 5 min @guard(models.site_stats_params) def site_stat_5min(self, start=None, end=None, site='default'): ''' Get site stats (5 min) ------------------------- returns an array of 5 min stats for the given site params: Name | required | description ----------------------------------------- start | False | Unix timestamp in seconds or datetime, defaults to end - 12h end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site name to get stats, defaults to `default` # supported from versions 5.5.* and later # retention policy for 5 minutes stats must be set accordingly ''' return self._site_stats('5minutes', 12 * 60 * 60 * 1000, start, end, site) # site stats: hourly @guard(models.site_stats_params) def site_stat_hourly(self, start=None, end=None, site='default'): ''' Get site stats (hourly) ------------------------- returns an array of hourly stats for the given site params: Name | required | description ----------------------------------------- start | False | Unix timestamp in seconds or datetime, defaults to end - 7d end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site name to get stats, defaults to `default` # bytes not supported from versions 4.9.1 and later ''' return self._site_stats('hourly', 7 * 24 * 60 * 60 * 1000, start, end, site) # site stats: daily @guard(models.site_stats_params) def site_stat_daily(self, start=None, end=None, site='default'): ''' Get site stats (daily) ------------------------- returns an array of daily stats for the given site params: Name | required | description ----------------------------------------- start | False | Unix timestamp in seconds or datetime, defaults to end - 30d end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site name to get stats, defaults to `default` # bytes not supported from versions 4.9.1 and later ''' return self._site_stats('daily', 30 * 24 * 60 * 60 * 1000, start, end, site) # AP stats @requires_login @guard(models.ap_stats_params.merge(models._inner_stats_extras)) def _ap_stats(self, gran, def_range, ap_mac=None, start=None, end=None, site=None): ''' Get ap stats ------------------------- returns an array of stats for the given ap or for all aps on a given site params: Name | required | description ----------------------------------------- gran | True | granularity of the stats, only permitted: 5minutes, hourly, daily def_range | True | default range for the stats, only used if start wasn't given ap_mac | False | mac address of the ap start | False | Unix timestamp in seconds or datetime, defaults to end - 12h end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site of the ap, if not provided `find_device` will be used (or `default` if ap_mac wasn't provided) # support and restrictions apply from 'ap_stat_*' functions ''' end = end * 1000 if isinstance( end, int) else int(datetime.now().timestamp() * 1000) if end is None else int(end.timestamp() * 1000) start = start * 1000 if isinstance( start, int) else end - def_range if start is None else int( start.timestamp() * 1000) assert 0 < start < end, 'start must be before end (and both positive)' data = { 'attrs': ['bytes', 'num_sta', 'time'], 'start': start, 'end': end, } if ap_mac is not None: site = self.find_device(ap_mac) if site is None else site assert site is not None, 'No site provided and AP does not belong to any known site' data['mac'] = ap_mac elif site is None: site = 'default' r = self.get(self.endpoint('/api/s/%s/stat/report/%s.ap' % (site, gran)), json=data) return self.process_response(r) # ap stats: 5 min @guard(models.ap_stats_params) def ap_stat_5min(self, ap_mac=None, start=None, end=None, site=None): ''' Get ap stats (5 min) ------------------------- returns an array of 5 min stats for the given ap or for all aps on a given site params: Name | required | description ----------------------------------------- ap_mac | False | mac address of the ap start | False | Unix timestamp in seconds or datetime, defaults to end - 12h end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site of the ap, if not provided `find_device` will be used (or `default` if ap_mac wasn't provided) # supported from versions 5.5.* and later # retention policy for 5 minutes stats must be set accordingly ''' return self._ap_stats('5minutes', 12 * 60 * 60 * 1000, ap_mac, start, end, site) # ap stats: hourly @guard(models.ap_stats_params) def ap_stat_hourly(self, ap_mac=None, start=None, end=None, site=None): ''' Get ap stats (hourly) ------------------------- returns an array of hourly stats for the given ap or for all aps on a given site params: Name | required | description ----------------------------------------- ap_mac | False | mac address of the ap start | False | Unix timestamp in seconds or datetime, defaults to end - 7d end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site of the ap, if not provided `find_device` will be used (or `default` if ap_mac wasn't provided) # versions < 4.6.6 keep up to 5 hours of these stats ''' return self._ap_stats('hourly', 7 * 24 * 60 * 60 * 1000, ap_mac, start, end, site) # ap stats: daily @guard(models.ap_stats_params) def ap_stat_daily(self, ap_mac=None, start=None, end=None, site=None): ''' Get ap stats (daily) ------------------------- returns an array of daily stats for the given ap or for all aps on a given site params: Name | required | description ----------------------------------------- ap_mac | True | mac address of the ap start | False | Unix timestamp in seconds or datetime, defaults to end - 7d end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site of the ap, if not provided `find_device` will be used (or `default` if ap_mac wasn't provided) # versions < 4.6.6 keep up to 5 hours of these stats ''' return self._ap_stats('daily', 7 * 24 * 60 * 60 * 1000, ap_mac, start, end, site) @requires_login @guard(models.user_stats_params.merge(models._inner_stats_extras)) def _user_stats(self, gran, def_range, user_mac, attrs=['rx_bytes', 'tx_bytes'], start=None, end=None, site=None): ''' Get ap stats ------------------------- returns an array of stats for the given ap or for all aps on a given site params: Name | required | description ----------------------------------------- gran | True | granularity of the stats, only permitted: 5minutes, hourly, daily def_range | True | default range for the stats, only used if start wasn't given user_mac | True | mac address of the user start | False | Unix timestamp in seconds or datetime, defaults to end - 12h end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site? attrs | False | list of attributes to be returned, defalts to [rx_bytes, tx_bytes] # support and restrictions apply from 'user_stat_*' functions ''' end = end * 1000 if isinstance( end, int) else int(datetime.now().timestamp() * 1000) if end is None else int(end.timestamp() * 1000) start = start * 1000 if isinstance( start, int) else end - def_range if start is None else int( start.timestamp() * 1000) assert 0 < start < end, 'start must be before end (and both positive)' data = { 'mac': user_mac, 'attrs': attrs if 'time' in attrs else attrs.append('time'), 'start': start, 'end': end, } # site? r = self.get(self.endpoint('/api/s/%s/stat/report/%s.user' % (site, gran)), json=data) return self.process_response(r) @guard(models.user_stats_params) def user_stat_5min(self, user_mac, attrs=['rx_bytes', 'tx_bytes'], start=None, end=None, site=None): ''' Get user/client stats (5 min) ------------------------- returns an array of 5 min stats for the given user/client params: Name | required | description ----------------------------------------- user_mac | True | mac address of the user start | False | Unix timestamp in seconds or datetime, defaults to end - 12h end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site? attrs | False | list of attributes to be returned, defalts to [rx_bytes, tx_bytes] # Possible values for attrs: - rx_bytes - tx_bytes - signal - rx_rate - tx_rate - rx_retries - tx_retries - rx_packets - tx_packets # supported from versions 5.8.* and later # retention policy for 5 minutes stats must be set accordingly # "Clients Historical Data" must be enabled (controller settings on Maintenance section) ''' return self._user_stats('5minutes', 12 * 60 * 60 * 1000, user_mac, attrs, start, end, site) @guard(models.user_stats_params) def user_stat_hourly(self, user_mac, attrs=['rx_bytes', 'tx_bytes'], start=None, end=None, site=None): ''' Get user/client stats (hourly) ------------------------- returns an array of hourly stats for the given user/client params: Name | required | description ----------------------------------------- user_mac | True | mac address of the user start | False | Unix timestamp in seconds or datetime, defaults to end - 7d end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site? attrs | False | list of attributes to be returned, defalts to [rx_bytes, tx_bytes] # Possible values for attrs: - rx_bytes - tx_bytes - signal - rx_rate - tx_rate - rx_retries - tx_retries - rx_packets - tx_packets # supported from versions 5.8.* and later # "Clients Historical Data" must be enabled (controller settings on Maintenance section) ''' return self._user_stats('hourly', 7 * 24 * 60 * 60 * 1000, user_mac, attrs, start, end, site) @guard(models.user_stats_params) def user_stat_daily(self, user_mac, attrs=['rx_bytes', 'tx_bytes'], start=None, end=None, site=None): ''' Get user/client stats (daily) ------------------------- returns an array of daily stats for the given user/client params: Name | required | description ----------------------------------------- user_mac | True | mac address of the user start | False | Unix timestamp in seconds or datetime, defaults to end - 7d end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site? attrs | False | list of attributes to be returned, defalts to [rx_bytes, tx_bytes] # Possible values for attrs: - rx_bytes - tx_bytes - signal - rx_rate - tx_rate - rx_retries - tx_retries - rx_packets - tx_packets # supported from versions 5.8.* and later # "Clients Historical Data" must be enabled (controller settings on Maintenance section) ''' return self._user_stats('daily', 7 * 24 * 60 * 60 * 1000, user_mac, attrs, start, end, site) # gateway @requires_login @guard(models.gateway_stats_params.merge(models._inner_stats_extras)) def _gateway_stats(self, gran, def_range, attrs=['mem', 'cpu', 'loadavg_5'], start=None, end=None, site='default'): ''' Get gateway stats ------------------------- returns an array of stats for the given ap or for all aps on a given site params: Name | required | description ----------------------------------------- gran | True | granularity of the stats, only permitted: 5minutes, hourly, daily def_range | True | default range for the stats, only used if start wasn't given attrs | False | list of attributes to be returned, defalts to [mem, cpu, loadavg_5] start | False | Unix timestamp in seconds or datetime, defaults to end - 12h end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site where the USG is located # support and restrictions apply from 'gateway_stat_*' functions ''' end = end * 1000 if isinstance( end, int) else int(datetime.now().timestamp() * 1000) if end is None else int(end.timestamp() * 1000) start = start * 1000 if isinstance( start, int) else end - def_range if start is None else int( start.timestamp() * 1000) assert 0 < start < end, 'start must be before end (and both positive)' data = { 'attrs': attrs if 'time' in attrs else attrs.append('time'), 'start': start, 'end': end, } r = self.get(self.endpoint('/api/s/%s/stat/report/%s.gw' % (site, gran)), json=data) return self.process_response(r) @guard(models.gateway_stats_params) def gateway_stat_5min(self, attrs=['mem', 'cpu', 'loadavg_5'], start=None, end=None, site='default'): ''' Get gateway stats (5 min) ------------------------- returns an array of 5 min stats for the given gateway belonging to the given site params: Name | required | description ----------------------------------------- attrs | False | list of attributes to be returned, defalts to [mem, cpu, loadavg_5] start | False | Unix timestamp in seconds or datetime, defaults to end - 12h end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site where the USG is located # Possible values for attrs: - mem - cpu - loadavg_5 - lan-rx_errors - lan-tx_errors - lan-rx_bytes - lan-tx_bytes - lan-rx_packets - lan-tx_packets - lan-rx_dropped - lan-tx_dropped # supported from versions 5.5.* and later # retention policy for 5 minutes stats must be set accordingly # must have a USG on the site ''' return self._gateway_stats('5minutes', 12 * 60 * 60 * 1000, attrs, start, end, site) @guard(models.gateway_stats_params) def gateway_stat_hourly(self, attrs=['mem', 'cpu', 'loadavg_5'], start=None, end=None, site='default'): ''' Get gateway stats (hourly) ------------------------- returns an array of hourly stats for the given gateway belonging to the given site params: Name | required | description ----------------------------------------- attrs | False | list of attributes to be returned, defalts to [mem, cpu, loadavg_5] start | False | Unix timestamp in seconds or datetime, defaults to end - 7d end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site where the USG is located # Possible values for attrs: - mem - cpu - loadavg_5 - lan-rx_errors - lan-tx_errors - lan-rx_bytes - lan-tx_bytes - lan-rx_packets - lan-tx_packets - lan-rx_dropped - lan-tx_dropped # must have a USG on the site ''' return self._gateway_stats('hourly', 7 * 24 * 60 * 60 * 1000, attrs, start, end, site) @guard(models.gateway_stats_params) def gateway_stat_daily(self, attrs=['mem', 'cpu', 'loadavg_5'], start=None, end=None, site='default'): ''' Get user/client stats (daily) ------------------------- returns an array of daily stats for the given gateway belonging to the given site params: Name | required | description ----------------------------------------- attrs | False | list of attributes to be returned, defalts to [mem, cpu, loadavg_5] start | False | Unix timestamp in seconds or datetime, defaults to end - 1y end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site where the USG is located # Possible values for attrs: - mem - cpu - loadavg_5 - lan-rx_errors - lan-tx_errors - lan-rx_bytes - lan-tx_bytes - lan-rx_packets - lan-tx_packets - lan-rx_dropped - lan-tx_dropped # must have a USG on the site ''' return self._gateway_stats('daily', 365 * 24 * 60 * 60 * 1000, attrs, start, end, site) @requires_login @guard(models._base_time_site_params) def speedtest_result(self, start=None, end=None, site='default'): ''' Get speed test results ------------------------- returns an array of speedtest results for the USG on the given site params: Name | required | description ----------------------------------------- start | False | Unix timestamp in seconds or datetime, defaults to end - 1d end | False | Unix timestamp in seconds or datetime, defaults to now site | False | site where the USG is located # must have a USG on the site ''' end = end * 1000 if isinstance( end, int) else int(datetime.now().timestamp() * 1000) if end is None else int(end.timestamp() * 1000) start = start * 1000 if isinstance( start, int) else end - 24 * 60 * 60 * 1000 if start is None else int( start.timestamp() * 1000) assert 0 < start < end, 'start must be before end (and both positive)' data = { 'attrs': ['xput_download', 'xput_upload', 'latency', 'time'], 'start': start, 'end': end, } r = self.get(self.endpoint('/api/s/%s/stat/report/archive.speedtest' % site), json=data) return self.process_response(r)
from ..context import Context from ..exception import InvalidAPIParameters from ..utils import ( CheckParamSource, check_params, ) from ..types import SENTINEL log = BraceStyleAdapter(logging.getLogger(__name__)) DEFAULT_CHUNK_SIZE: Final = 256 * 1024 # 256 KiB DEFAULT_INFLIGHT_CHUNKS: Final = 8 download_token_data_iv = t.Dict({ t.Key('op'): t.Atom('download'), t.Key('volume'): t.String, t.Key('vfid'): tx.UUID, t.Key('relpath'): t.String, t.Key('archive', default=False): t.Bool, t.Key('unmanaged_path', default=None): t.Null | t.String, }).allow_extra('*') # allow JWT-intrinsic keys upload_token_data_iv = t.Dict({ t.Key('op'): t.Atom('upload'), t.Key('volume'): t.String, t.Key('vfid'): tx.UUID, t.Key('relpath'): t.String, t.Key('session'): t.String, t.Key('size'): t.Int, }).allow_extra('*') # allow JWT-intrinsic keys
from ai.backend.common.logging import BraceStyleAdapter from ..abc import AbstractVolume from ..context import Context from ..exception import InvalidAPIParameters from ..types import SENTINEL from ..utils import CheckParamSource, check_params log = BraceStyleAdapter(logging.getLogger(__name__)) DEFAULT_CHUNK_SIZE: Final = 256 * 1024 # 256 KiB DEFAULT_INFLIGHT_CHUNKS: Final = 8 download_token_data_iv = t.Dict( { t.Key("op"): t.Atom("download"), t.Key("volume"): t.String, t.Key("vfid"): tx.UUID, t.Key("relpath"): t.String, t.Key("archive", default=False): t.Bool, t.Key("unmanaged_path", default=None): t.Null | t.String, }, ).allow_extra("*", ) # allow JWT-intrinsic keys upload_token_data_iv = t.Dict( { t.Key("op"): t.Atom("upload"), t.Key("volume"): t.String, t.Key("vfid"): tx.UUID, t.Key("relpath"): t.String, t.Key("session"): t.String, t.Key("size"): t.Int,
class TestScalar(unittest.TestCase): TRAFARET = T.Dict({ T.Key("a_null", optional=True): T.Null, T.Key("a_bool", optional=True): T.Bool, T.Key("a_float", optional=True): T.Float, T.Key("a_int", optional=True): T.Int, T.Key("a_atom_str", optional=True): T.Atom("hello"), T.Key("a_atom_list", optional=True): T.Atom(["x", "y"]), T.Key("a_enum_str", optional=True): T.Enum(["x", "y"]), T.Key("a_str", optional=True): T.String(max_length=12), T.Key("a_email", optional=True): T.Email, T.Key("a_url", optional=True): T.URL, }) def test_null(self): self.assertEqual(get_err(self.TRAFARET, u""" a_null: "hello" """), dedent(u"""\ config.yaml:2: a_null: value should be None -> 'hello' """)) def test_bool(self): self.assertEqual(get_err(self.TRAFARET, u""" a_bool: "hello" """), dedent(u"""\ config.yaml:2: a_bool: value should be True or False -> 'hello' """)) def test_float(self): self.assertEqual(get_err(self.TRAFARET, u""" a_float: "hello" """), dedent(u"""\ config.yaml:2: a_float: value can't be converted to float -> 'hello' """)) def test_int(self): self.assertEqual(get_err(self.TRAFARET, u""" a_int: 2.57 """), dedent(u"""\ config.yaml:2: a_int: value is not int -> 2.57 """)) def test_atom_str(self): self.assertEqual(get_err(self.TRAFARET, u""" a_atom_str: "xxx" """), dedent(u"""\ config.yaml:2: a_atom_str: value is not exactly 'hello' -> 'xxx' """)) def test_atom_list(self): self.assertEqual(get_err(self.TRAFARET, u""" a_atom_list: "xxx" """), dedent(u"""\ config.yaml:2: a_atom_list: value is not exactly '['x', 'y']' -> 'xxx' """)) def test_enum_str(self): self.assertEqual(get_err(self.TRAFARET, u""" a_enum_str: "hello" """), dedent(u"""\ config.yaml:2: a_enum_str: value doesn't match any variant -> 'hello' """)) def test_string(self): self.assertEqual(get_err(self.TRAFARET, u""" a_str: 1 """), dedent(u"""\ config.yaml:2: a_str: value is not a string -> 1 """)) def test_long_string(self): self.assertEqual(get_err(self.TRAFARET, u""" a_str: "hello my good friends" """), dedent(u"""\ config.yaml:2: a_str: String is longer than 12 characters -> 'hello my good friends' """)) def test_email(self): self.assertEqual(get_err(self.TRAFARET, u""" a_email: "hello" """), dedent(u"""\ config.yaml:2: a_email: value is not a valid email address -> 'hello' """)) def test_url(self): self.assertEqual(get_err(self.TRAFARET, u""" a_url: "hello" """), dedent(u"""\ config.yaml:2: a_url: value is not URL -> 'hello' """))
def test_validate_payload(): raw_data = b'{"foo": "bar"}' schema = t.Dict({t.Key('foo'): t.Atom('bar')}) data = validate_payload(raw_data, schema) assert data == {'foo': 'bar'}
Not, Pattern, unique_strings_list, ensure_list, ) from .decimal import Decimal from .format import format_trafaret __VERSION__ = (0, 2, 1) check_number = t.OnError(t.Float() | Decimal(), 'Not a number') json_schema_type = ( t.Atom('null') & just(t.Null()) | t.Atom('boolean') & just(t.Bool()) | t.Atom('object') & just(t.Type(dict)) | t.Atom('array') & just(t.Type(list)) | t.Atom('number') & just(check_number) | t.Atom('integer') & just(t.Int()) | t.Atom('string') & just(t.String()) ) def multipleOf(multiplier): def check(value): if value % multiplier != 0: return t.DataError('%s is not devisible by %s' % (value, multiplier)) return value return check
'data': t.Or(t.List(t.Any), t.String), # check if data is optinal, and possible values 'meta': t.Dict( { 'rc': t.Enum('ok', 'error'), # check if other rc's could exist t.Key('msg', optional=True): t.String, }, ignore_extra='*'), }, ignore_extra='*') _base_time_params = t.Dict({ 'start': t.Or(t.Float, t.Type(datetime), t.Atom(None)), 'end': t.Or(t.Float, t.Type(datetime), t.Atom(None)), }) _base_time_site_params = _base_time_params.merge({'site': SiteName}) _base_time_op_site_params = _base_time_params.merge( {'site': t.Or(SiteName, t.Atom(None))}) _inner_stats_extras = t.Dict({ 'gran': t.Enum('5minutes', 'hourly', 'daily'), 'def_range': t.Int, }) # will not check for port in base_url for now init_params = t.Dict({
def test_and(self): indeed_int = t.Atom('123') & int self.assertEqual(indeed_int('123'), 123) # fixed 0.8.0 error
class TestEasyAlternatives(unittest.TestCase): maxDiff = 8192 TRAFARET = T.Dict({ "connection": T.Or( T.Dict({ "kind": T.Atom("unix"), "path": T.String(), }), T.Dict({ "kind": T.Atom("tcp"), "host": T.String(), "port": T.Int, }), ), }) if BEAUTY_ERROR: def test_tcp(self): self.assertEqual(get_err(self.TRAFARET, u""" connection: kind: tcp host: localhost port: http """), dedent(u"""\ config.yaml:2: connection.port: value can't be converted to int (where .kind is 'tcp') """)) def test_unix(self): self.assertEqual(get_err(self.TRAFARET, u""" connection: kind: unix path: /tmp/sock port: http """), dedent(u"""\ config.yaml:2: connection.port: port is not allowed key (where .kind is 'unix') """)) else: def test_tcp(self): self.assertEqual(get_err(self.TRAFARET, u""" connection: kind: tcp host: localhost port: http """), dedent(u"""\ config.yaml:3: connection[0].host: host is not allowed key config.yaml:3: connection[0].kind: value is not exactly 'unix' config.yaml:3: connection[0].path: is required config.yaml:3: connection[0].port: port is not allowed key config.yaml:3: connection[1].port: value can't be converted to int """)) def test_unix(self): self.assertEqual(get_err(self.TRAFARET, u""" connection: kind: unix path: /tmp/sock port: http """), dedent(u"""\ config.yaml:3: connection[0].port: port is not allowed key config.yaml:3: connection[1].host: is required config.yaml:3: connection[1].kind: value is not exactly 'tcp' config.yaml:3: connection[1].path: path is not allowed key config.yaml:3: connection[1].port: value can't be converted to int """))
def test_raise_error(self): other = lambda v: DataError('other error') fail_other = t.Atom('a') & other res = extract_error(fail_other, 'a') self.assertEqual(res, 'other error')
def test_atom(self): res = t.Atom('atom').check('atom') assert res == 'atom' err = extract_error(t.Atom('atom'), 'molecule') assert err == "value doesn't match any variant"