def test_lri(): cache_size = 10 bc = LRI(cache_size, on_miss=lambda k: k.upper()) for idx, char in enumerate(string.ascii_letters): x = bc[char] assert x == char.upper() least_recent_insert_index = idx - cache_size if least_recent_insert_index >= 0: # least recently inserted object evicted assert len(bc) == cache_size for char in string.ascii_letters[least_recent_insert_index+1:idx]: assert char in bc # test that reinserting an exising key changes eviction behavior bc[string.ascii_letters[-cache_size+1]] = "new value" least_recently_inserted_key = string.ascii_letters[-cache_size+2] bc["unreferenced_key"] = "value" keys_in_cache = [ string.ascii_letters[i] for i in range(-cache_size + 1, 0) if string.ascii_letters[i] != least_recently_inserted_key ] keys_in_cache.append("unreferenced_key") assert len(bc) == cache_size for k in keys_in_cache: assert k in bc
def test_lri_cache_eviction(): """ Regression test Original LRI implementation had a bug where the specified cache size only supported `max_size` number of inserts to the cache, rather than support `max_size` number of keys in the cache. This would result in some unintuitive behavior, where a key is evicted recently inserted value would be evicted from the cache if the key inserted was inserted `max_size` keys earlier. """ test_cache = LRI(2) # dequeue: (key1); dict keys: (key1) test_cache["key1"] = "value1" # dequeue: (key1, key1); dict keys: (key1) test_cache["key1"] = "value1" # dequeue: (key1, key1, key2); dict keys: (key1, key2) test_cache["key2"] = "value2" # dequeue: (key1, key2, key3); dict keys: (key2, key3) test_cache["key3"] = "value3" # will error here since we evict key1 from the cache and it doesn't # exist in the dict anymore test_cache["key3"] = "value3"
def test_lri(): bc = LRI(10, on_miss=lambda k: k.upper()) for char in string.ascii_letters: x = bc[char] assert x == char.upper() assert len(bc) == 10
def __init__(self, cache=None): self.h_cache = LRI() if cache is None else cache self.door_count = 0 self.hook_count = 0 self.hand_count = 0
) import inspect from glob import glob from importlib import import_module from os import path from boltons.cacheutils import LRI, cached from .. import logger from ..base import PackageManager # Use a cache to keep re-computing the global pool of registered manager # definitions. @cached(LRI(max_size=1)) def pool(): """ Search for package manager definitions locally and return a dict. Is considered valid package manager definitions classes which: 1 - are sub-classes of PackageManager, and 2 - is not PackageManager itself, and 3 - are located in files at the same level or below this one, and 4 - have a non null cli_name property. """ register = {} here = path.dirname(path.abspath(__file__)) for py_file in glob(path.join(here, '*.py')): module_name = path.splitext(path.basename(py_file))[0]
for straight_mapping in [ RESERVED_COUNTRY_CODES, COUNTRY_ALIASES, SUBDIVISION_ALIASES, FOREIGN_TERRITORIES_MAPPING, ]: for alias_code, target_code in straight_mapping.items(): mapping.setdefault(alias_code, set()).add(target_code) return mapping REVERSE_MAPPING = generate_mapping() @cached(LRI()) def supported_territory_codes() -> Set[str]: """Return a set of recognized territory codes.""" return supported_country_codes().union(supported_subdivision_codes()) @cached(LRI()) def supported_country_codes() -> Set[str]: """Return a set of recognized country codes. Are supported: * ISO 3166-1 alpha-2 country codes and exceptional reservations * European Commision country code exceptions """ return {country.alpha_2 for country in countries} | set({
from boltons.cacheutils import LRI __all__ = [ 'get_intent' ] cache = LRI(max_size=1) def get_intent(*sents, **kwargs): if 'intent' not in cache: from eva.intents.train import IntentClassifier model_file = kwargs.pop( 'model', 'intents.model' ) classifier = IntentClassifier() cache['intent'] = classifier.load(model_file) return cache['intent'].predict(sents)
class PluginConfig(BaseModel): """ A helper base class for easier declarative plugin configuration. - can be used with [Configuration.parse_sub_config][releaseherald.configuration.Configuration.parse_sub_config] for easier parsing - Attributes can be Annotated types, which can contain [FromCommandline][releaseherald.plugins.plugin_config.FromCommandline] Annotation, that make the config setting overridable from commandline #Usage ```python class MyPluginConfig(PluginConfig): non_overridable_value: str = "some default # use type Annotation to connect the attribute wit the commandline Option overridable_value: Annotated[str, FromCommandline( "generate", click.Option( param_decls=["--override"], help="override the overrideable value", ) )] = "default for overrideable value" class MyPlugin: @releaseherald.plugins.hookimpl def process_config(self, config: Configuration): # parse the config with the helper self.my_config = config.parse_sub_config("my_config", MyPluginConfig) @releaseherald.plugins.hookimpl def get_command_options(self, command: str) -> Optional[CommandOptions]: # just use the helper to return the right thing return self.my_config.get_command_options(command) @releaseherald.plugins.hookimpl def on_start_command(self, command: str, kwargs: Dict[str, Any]): # use the helper to reflect commandline overrides in the config self.my_config.update(command, kwargs) ``` """ @classmethod @cached(LRI()) def _get_command_options_info(cls) -> Dict[str, CommandOptionsInfo]: command_options: DefaultDict[str, CommandOptionsInfo] = defaultdict( CommandOptionsInfo ) for field in cls.__fields__.values(): metadata = getattr(field.outer_type_, "__metadata__", None) if not metadata: continue for annotation in metadata: if not isinstance(annotation, FromCommandline): continue command = command_options[annotation.command] command.options.append(annotation.option) command.update_mappings.append( UpdateMapping(field.name, annotation.option.name) ) return dict(command_options) def get_command_options(self, command: str) -> Optional[CommandOptions]: """ Generate command options from Annotated fields which can be returned directly from [get_command_options hook][releaseherald.plugins.hookspecs.get_command_options] Args: command: the command these command options are registered with Returns: The command options that the [get_command_options hook][releaseherald.plugins.hookspecs.get_command_options] expects """ command_options: CommandOptionsInfo = ( self._get_command_options_info().get(command) ) if command_options: def default_opts_callback(default_options: Dict[str, Any]): for update_mapping in command_options.update_mappings: default_options[update_mapping.option_name] = getattr( self, update_mapping.field_name ) return CommandOptions( command_options.options, default_opts_callback ) def update(self, command: str, kwargs: Dict[str, Any]) -> None: """ Update itself from commandline options, can be used in [on_start_command hook][releaseherald.plugins.hookspecs.on_start_command] Args: command: the command kwargs: the commandline args for the command """ command_options: CommandOptionsInfo = ( self._get_command_options_info().get(command) ) if command_options: for update_mapping in command_options.update_mappings: setattr( self, update_mapping.field_name, kwargs[update_mapping.option_name], )
def search(ctx, extended, exact, query): """ Search packages from all managers. """ active_managers = ctx.obj['active_managers'] output_format = ctx.obj['output_format'] sort_by = ctx.obj['sort_by'] stats = ctx.obj['stats'] # Build-up a global list of package matches per manager. matches = {} for manager in active_managers: matches[manager.id] = { 'id': manager.id, 'name': manager.name, 'packages': list(manager.search(query, extended, exact).values()) } # Serialize errors at the last minute to gather all we encountered. matches[manager.id]['errors'] = list( {expt.error for expt in manager.cli_errors}) # Machine-friendly data rendering. if output_format == 'json': click.echo(json(matches)) return # Prepare highlighting helpers. query_parts = {query}.union(map(str, TokenizedString(query))) @cached(LRI(max_size=1000)) def highlight(string): # Ranges of character indices flagged for highlighting. ranges = set() # TODO: Fix upper-case matching, as tokenizer lower them down. for part in query_parts: # Search for occurrences of query parts in original string. if part in string: # Flag matching substrings for highlighting. occurrences = [ match.start() for match in re.finditer(part, string) ] for match_start in occurrences: match_end = match_start + len(part) - 1 ranges.add('{}-{}'.format(match_start, match_end)) # Reduce index ranges, compute complement ranges, transform them to # list of integers. ranges = ','.join(ranges) bold_ranges = int_ranges_from_int_list(ranges) normal_ranges = int_ranges_from_int_list( complement_int_list(ranges, range_end=len(string))) # Apply style to range of characters flagged as matching. styled_str = '' for i, j in sorted(bold_ranges + normal_ranges): segment = getitem(string, slice(i, j + 1)) if (i, j) in bold_ranges: segment = click.style(segment, bold=True) styled_str += segment return styled_str # Human-friendly content rendering. table = [] for manager_id, matching_pkg in matches.items(): table += [[ highlight(info['name']), highlight(info['id']), manager_id, info['latest_version'] if info['latest_version'] else '?' ] for info in matching_pkg['packages']] # Sort and print table. print_table([('Package name', 'package_name'), ('ID', 'package_id'), ('Manager', 'manager_id'), ('Latest version', 'version')], table, sort_by) if stats: print_stats(matches)
def search(ctx, extended, exact, query): """ Search packages from all managers. """ active_managers = ctx.obj['active_managers'] output_format = ctx.obj['output_format'] sort_by = ctx.obj['sort_by'] stats = ctx.obj['stats'] # Build-up a global list of package matches per manager. matches = {} for manager in active_managers: matches[manager.id] = { 'id': manager.id, 'name': manager.name, 'packages': list(manager.search(query, extended, exact).values())} # Serialize errors at the last minute to gather all we encountered. matches[manager.id]['errors'] = list({ expt.error for expt in manager.cli_errors}) # Machine-friendly data rendering. if output_format == 'json': click.echo(json(matches)) return # Prepare highlighting helpers. query_parts = {query}.union(map(str, TokenizedString(query))) # XXX Local copy of boltons.strutils.complement_int_list(). # TODO: Replace with the boltons util function above once 20.1.1.dev0 is # released. def complement_int_list( range_string, range_start=0, range_end=None, delim=',', range_delim='-'): """ Returns range string that is the complement of the one provided as *range_string* parameter. These range strings are of the kind produce by :func:`format_int_list`, and parseable by :func:`parse_int_list`. Args: range_string (str): String of comma separated positive integers or ranges (e.g. '1,2,4-6,8'). Typical of a custom page range string used in printer dialogs. range_start (int): A positive integer from which to start the resulting range. Value is inclusive. Defaults to ``0``. range_end (int): A positive integer from which the produced range is stopped. Value is exclusive. Defaults to the maximum value found in the provided ``range_string``. delim (char): Defaults to ','. Separates integers and contiguous ranges of integers. range_delim (char): Defaults to '-'. Indicates a contiguous range of integers. >>> complement_int_list('1,3,5-8,10-11,15') '0,2,4,9,12-14' >>> complement_int_list('1,3,5-8,10-11,15', range_start=0) '0,2,4,9,12-14' >>> complement_int_list('1,3,5-8,10-11,15', range_start=1) '2,4,9,12-14' >>> complement_int_list('1,3,5-8,10-11,15', range_start=2) '2,4,9,12-14' >>> complement_int_list('1,3,5-8,10-11,15', range_start=3) '4,9,12-14' >>> complement_int_list('1,3,5-8,10-11,15', range_end=15) '0,2,4,9,12-14' >>> complement_int_list('1,3,5-8,10-11,15', range_end=14) '0,2,4,9,12-13' >>> complement_int_list('1,3,5-8,10-11,15', range_end=13) '0,2,4,9,12' >>> complement_int_list('1,3,5-8,10-11,15', range_end=20) '0,2,4,9,12-14,16-19' >>> complement_int_list('1,3,5-8,10-11,15', range_end=0) '' >>> complement_int_list('1,3,5-8,10-11,15', range_start=-1) '0,2,4,9,12-14' >>> complement_int_list('1,3,5-8,10-11,15', range_end=-1) '' >>> complement_int_list('1,3,5-8', range_start=1, range_end=1) '' >>> complement_int_list('1,3,5-8', range_start=2, range_end=2) '' >>> complement_int_list('1,3,5-8', range_start=2, range_end=3) '2' >>> complement_int_list('1,3,5-8', range_start=-10, range_end=-5) '' >>> complement_int_list('1,3,5-8', range_start=20, range_end=10) '' >>> complement_int_list('') '' """ int_list = set(parse_int_list(range_string, delim, range_delim)) if range_end is None: if int_list: range_end = max(int_list) + 1 else: range_end = range_start complement_values = set( range(range_end)) - int_list - set(range(range_start)) return format_int_list(complement_values, delim, range_delim) # XXX Local copy of boltons.strutils.int_ranges_from_int_list(). # TODO: Replace with the boltons util function above once 20.1.1.dev0 is # released. def int_ranges_from_int_list(range_string, delim=',', range_delim='-'): """ Transform a string of ranges (*range_string*) into a tuple of tuples. Args: range_string (str): String of comma separated positive integers or ranges (e.g. '1,2,4-6,8'). Typical of a custom page range string used in printer dialogs. delim (char): Defaults to ','. Separates integers and contiguous ranges of integers. range_delim (char): Defaults to '-'. Indicates a contiguous range of integers. >>> int_ranges_from_int_list('1,3,5-8,10-11,15') ((1, 1), (3, 3), (5, 8), (10, 11), (15, 15)) >>> int_ranges_from_int_list('1') ((1, 1),) >>> int_ranges_from_int_list('') () """ int_tuples = [] # Normalize the range string to our internal format for processing. range_string = format_int_list( parse_int_list(range_string, delim, range_delim)) if range_string: for bounds in range_string.split(','): if '-' in bounds: start, end = bounds.split('-') else: start, end = bounds, bounds int_tuples.append((int(start), int(end))) return tuple(int_tuples) @cached(LRI(max_size=1000)) def highlight(string): # Ranges of character indices flagged for highlighting. ranges = set() # TODO: Fix upper-case matching, as tokenizer lower them down. for part in query_parts: # Search for occurences of query parts in original string. if part in string: # Flag matching substrings for highlighting. occurences = [ match.start() for match in re.finditer(part, string)] for match_start in occurences: match_end = match_start + len(part) - 1 ranges.add('{}-{}'.format(match_start, match_end)) # Reduce index ranges, compute complement ranges, transform them to # list of integers. ranges = ','.join(ranges) bold_ranges = int_ranges_from_int_list(ranges) normal_ranges = int_ranges_from_int_list( complement_int_list(ranges, range_end=len(string))) # Apply style to range of characters flagged as matching. styled_str = '' for i, j in sorted(bold_ranges + normal_ranges): segment = getitem(string, slice(i, j + 1)) if (i, j) in bold_ranges: segment = click.style(segment, bold=True) styled_str += segment return styled_str # Human-friendly content rendering. table = [] for manager_id, matching_pkg in matches.items(): table += [[ highlight(info['name']), highlight(info['id']), manager_id, info['latest_version'] if info['latest_version'] else '?'] for info in matching_pkg['packages']] # Sort and print table. print_table([ ('Package name', 'package_name'), ('ID', 'package_id'), ('Manager', 'manager_id'), ('Latest version', 'version')], table, sort_by) if stats: print_stats(matches)