PK!~typegql/__init__.pyfrom .core.arguments import (Argument, ArgumentList, Argument, ArgumentList, InputArgument, ListInputArgument, RequiredInputArgument, RequiredListInputArgument, RequiredArgument, RequiredArgumentList ) from .core.connection import Connection from .core.fields import Field, OptionalField, ReadonlyField from .core.graph import Graph from .core.schema import Schema from .core.types import ID, DateTime __all__ = ( 'Graph', 'Argument', 'ArgumentList', 'InputArgument', 'ListInputArgument', 'RequiredInputArgument', 'RequiredListInputArgument', 'RequiredArgument', 'RequiredArgumentList', 'Connection', 'Field', 'OptionalField', 'ReadonlyField', 'Schema', 'ID', 'DateTime' ) PK!3typegql/client/__init__.pyfrom .client import Client PK!Y typegql/client/client.pyfrom typing import overload, Dict, Tuple import aiohttp from graphql import get_introspection_query, build_client_schema, DocumentNode, ExecutionResult from typegql.client.dsl import DSLSchema class Client: """ Usage: async with Client(url) as client: await client.introspection() dsl = client.dsl query = dsl.Query.clients_connection.select(dsl.ClientConnection.total_count) doc = dsl.query(query) result = await client.execute(doc) """ def __init__(self, url: str, auth=None, headers: Dict = None, use_json=True, timeout=None, camelcase=True): self.url = url self.session: aiohttp.ClientSession = None self.dsl: DSLSchema = None self.auth = auth self.headers = headers self.use_json = use_json self.timeout = timeout self.camelcase = camelcase async def init(self): self.session = self.session or aiohttp.ClientSession() async def __aenter__(self): await self.init() return self async def __aexit__(self, exc_type, exc_val, exc_tb): await self.close() async def close(self): await self.session.close() async def introspection(self): status, result = await self.execute(get_introspection_query()) assert status == 200 schema = build_client_schema(result.data) self.dsl = DSLSchema(schema, camelcase=self.camelcase) return schema @overload async def execute(self, document: DocumentNode, variable_values=None, timeout=None) -> ExecutionResult: pass @overload async def execute(self, query: str, variable_values=None, timeout=None) -> ExecutionResult: pass async def execute(self, query: str, variable_values=None, timeout=None) -> Tuple[int, ExecutionResult]: if isinstance(query, DocumentNode): query = self.dsl.as_string(query) payload = { 'query': query, 'variables': variable_values or {} } if self.use_json: body = {'json': payload} else: body = {'data': payload} async with self.session.post(self.url, auth=self.auth, headers=self.headers, timeout=timeout or self.timeout, **body) as response: result = await response.json() if self.use_json else response.text() assert 'errors' in result or 'data' in result, f'Received non-compatible response "{result}"' return response.status, ExecutionResult( errors=result.get('errors'), data=result.get('data') ) PK!ߋtypegql/client/dsl.pyimport collections import decimal from functools import partial from graphql import GraphQLField, print_ast, ast_from_value, GraphQLNonNull, GraphQLInputField, GraphQLList, \ GraphQLEnumType, GraphQLInputObjectType, OperationType, GraphQLString from graphql.language import ast from graphql.pyutils import snake_to_camel class DSLField: def __init__(self, name, f, camelcase=True): self.field = f self.ast_field = ast.FieldNode(name=ast.NameNode(value=name), arguments=[]) self.selection_set = None self.camelcase = camelcase def select(self, *fields): if not self.ast_field.selection_set: self.ast_field.selection_set = ast.SelectionSetNode(selections=[]) self.ast_field.selection_set.selections.extend(selections(*fields)) return self def __call__(self, *args, **kwargs): return self.args(*args, **kwargs) def alias(self, alias): self.ast_field.alias = ast.NameNode(value=alias) return self def args(self, **kwargs): if self.camelcase: self.args_to_camelcase(kwargs) for name, value in kwargs.items(): arg = self.field.args.get(name) assert arg, f'Invalid argument {name} for field {self.name}' arg_type_serializer = get_arg_serializer(arg.type) value = arg_type_serializer(value) self.ast_field.arguments.append( ast.ArgumentNode( name=ast.NameNode(value=name), value=get_ast_value(value) ) ) return self def args_to_camelcase(self, arguments): if not isinstance(arguments, dict): return keys = [k for k in arguments.keys()] for key in keys: if isinstance(arguments[key], list): for arg in arguments[key]: self.args_to_camelcase(arg) arguments[snake_to_camel(key, upper=False)] = arguments.pop(key) @property def ast(self): return self.ast_field @property def name(self): return self.ast.name.value class DSLType(object): def __init__(self, _type, camelcase=True): self.type = _type self.camelcase = camelcase def __getattr__(self, name): formatted_name, field_def = self.get_field(name) return DSLField(formatted_name, field_def, camelcase=self.camelcase) def get_field(self, name): if self.camelcase: name = snake_to_camel(name, upper=False) if name in self.type.fields: return name, self.type.fields[name] raise KeyError('Field {} doesnt exist in type {}.'.format(name, self.type.name)) class DSLSchema(object): def __init__(self, schema, camelcase=True): self.schema = schema self.camelcase = camelcase def __getattr__(self, name): type_def = self.schema.get_type(name) return DSLType(type_def, self.camelcase) def query(self, *fields, operation=OperationType.QUERY) -> ast.DocumentNode: return ast.DocumentNode( definitions=[ast.OperationDefinitionNode( operation=operation, selection_set=ast.SelectionSetNode( selections=list(selections(*fields)) ) )] ) def mutation(self, *fields) -> ast.DocumentNode: return self.query(*fields, operation=OperationType.MUTATION) def as_string(self, doc): return print_ast(doc) def field(f, **args): if isinstance(f, GraphQLField): return DSLField(f).args(**args) elif isinstance(f, DSLField): return f raise Exception('Received incompatible query field: "{}".'.format(field)) def selections(*fields): for _field in fields: yield field(_field).ast def get_ast_value(value): if isinstance(value, ast.Node): return value if isinstance(value, ast.ValueNode): return value if isinstance(value, str): return ast.StringValueNode(value=value) elif isinstance(value, bool): return ast.BooleanValueNode(value=value) elif isinstance(value, (float, decimal.Decimal)): return ast.FloatValueNode(value=value) elif isinstance(value, int): return ast.IntValueNode(value=value) elif isinstance(value, list): return ast.ListValueNode(values=[get_ast_value(v) for v in value]) return None def serialize_list(serializer, values): if isinstance(values, str): values = [values] assert isinstance(values, collections.Iterable), 'Expected iterable, received "{}"'.format(repr(values)) result = list() for val in values: result.append(serializer(val)) return result def serialize_string(value): return ast.StringValueNode(value=value) def serialize_enum(arg_type, value): return ast.EnumValueNode(value=arg_type.serialize(value)) def serialize_input_object(arg_type, value): serializers = {k: get_arg_serializer(v) for k, v in arg_type.fields.items()} result = ast_from_value(value, arg_type) for f in result.fields: serialized = serializers[f.name.value](value[f.name.value]) if isinstance(f.value, ast.ListValueNode): f.value = ast.ListValueNode(values=serialized) else: f.value = serialized return result def get_arg_serializer(arg_type): if isinstance(arg_type, GraphQLNonNull): return get_arg_serializer(arg_type.of_type) if arg_type == GraphQLString: return serialize_string if isinstance(arg_type, GraphQLInputField): return get_arg_serializer(arg_type.type) if isinstance(arg_type, GraphQLList): inner_serializer = get_arg_serializer(arg_type.of_type) return partial(serialize_list, inner_serializer) if isinstance(arg_type, GraphQLEnumType): return partial(serialize_enum, arg_type) if isinstance(arg_type, GraphQLInputObjectType): return partial(serialize_input_object, arg_type) return partial(serialize_value, arg_type) def serialize_value(arg_type, value): return ast_from_value( str(value) if arg_type.serialize(value) is None else arg_type.serialize(value), arg_type ) PK!typegql/core/__init__.pyPK!ctypegql/core/arguments.pyfrom typing import Generic, TypeVar, List, Type, Any T = TypeVar('T', bound='Graph') class Argument: def __init__(self, _type: Type[Any], name: str, description: str = '', required: bool = False, is_input: bool = False): self._type = _type self.name = name self.description = description self.required = required self.is_input = is_input def __class_getitem__(cls, *args, **kwargs): assert len(args) == 1, 'GraphArgument container accepts a single argument' item = args[0] return cls(item, name='') def __call__(self, name: str, *args, **kwargs): self.name = name return self @property def type(self): return self._type class RequiredArgument(Argument, Generic[T]): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.required = True class ArgumentList(Argument): @property def type(self): return List[self._type] class RequiredArgumentList(RequiredArgument): @property def type(self): return List[self._type] class InputArgument(Argument): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.is_input = True class RequiredInputArgument(Argument, Generic[T]): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.required = True self.is_input = True class ListInputArgument(ArgumentList, Generic[T]): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.is_input = True @property def type(self): return List[self._type] class RequiredListInputArgument(ArgumentList, Generic[T]): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.required = True self.is_input = True @property def type(self): return List[self._type] PK!rktypegql/core/builder.pyfrom typing import get_type_hints, Type, Any, Dict import graphql from graphql.pyutils import snake_to_camel from .arguments import Argument, ArgumentList from .connection import Connection, Node, Edge, PageInfo, T from .fields import Field from .types import DateTime, Dictionary from .utils import is_enum, is_list, is_graph, is_connection class SchemaBuilder: def __init__(self, camelcase: bool, types: Dict = None): self.camelcase = camelcase self.types = { 'ID': graphql.GraphQLID, 'bool': graphql.GraphQLBoolean, 'int': graphql.GraphQLInt, 'float': graphql.GraphQLFloat, 'str': graphql.GraphQLString, 'datetime': DateTime(), 'Dict': Dictionary(), 'Decimal': graphql.GraphQLFloat } if isinstance(types, Dict): self.types.update(types) def get_fields(self, graph: Type[Any], is_mutation=False): result = dict() for name, _type in get_type_hints(graph).items(): field = getattr(graph, name, None) if not isinstance(field, Field): continue if is_mutation and not field.mutation: continue graph_type = self.map_type(_type, is_mutation=is_mutation) if not graph_type: continue if field.required: graph_type = graphql.GraphQLNonNull(graph_type) if is_connection(_type): field.arguments.extend(_type.page_arguments()) args = self.arguments(field, self.camelcase) field_name = field.name or name if self.camelcase: field_name = snake_to_camel(field_name, upper=False) if is_mutation: result[field_name] = graphql.GraphQLInputField(graph_type, description=field.description) else: result[field_name] = graphql.GraphQLField(graph_type, description=field.description, args=args) return result def map_type(self, _type: Any, is_mutation=False): if isinstance(_type, graphql.GraphQLType): return _type try: type_name = _type.__name__ except AttributeError: type_name = _type._name if not type_name: type_name = _type.__origin__.__name__ if is_connection(_type): return self.get_connection_fields(_type) if is_enum(_type): if type_name in self.types: return self.types.get(type_name) enum_type = graphql.GraphQLEnumType(type_name, _type) self.types[type_name] = enum_type return enum_type if is_list(_type): inner = self.map_type(_type.__args__[0], is_mutation=is_mutation) return graphql.GraphQLList(inner) if is_graph(_type): return self.build_object_type(type_name, _type, is_mutation=is_mutation) return self.types.get(type_name) def build_object_type(self, type_name, _type, is_mutation=False): if is_mutation: type_name = f'{type_name}Mutation' if type_name in self.types: return self.types[type_name] fields = self.get_fields(_type, is_mutation=is_mutation) if not is_mutation: graph_type = graphql.GraphQLObjectType(type_name, description=_type.__doc__, fields=fields) else: graph_type = graphql.GraphQLInputObjectType(type_name, description=_type.__doc__, fields=fields) self.types[type_name] = graph_type return graph_type def arguments(self, field: Field, camelcase=True): result: graphql.GraphQLArgumentMap = dict() arguments = field.arguments if not isinstance(arguments, (list, tuple)): return for arg in arguments: if not isinstance(arg, (Argument, ArgumentList)): continue _type = self.map_type(arg.type, is_mutation=arg.is_input) if arg.required: _type = graphql.GraphQLNonNull(_type) arg_name = snake_to_camel(arg.name, False) if camelcase else arg.name result[arg_name] = graphql.GraphQLArgument(_type, description=arg.description) return result def get_connection_fields(self, graph: Type[Connection]): if not is_connection(graph): return self.get_fields(graph) self.build_connection_interface() connection_class = graph.__origin__ wrapped = graph.__args__[0] fields = {} for name, _type in get_type_hints(connection_class).items(): field = getattr(graph, name, None) if not isinstance(field, Field): continue if is_list(_type) and _type.__args__[0] is Edge[T]: inner = _type.__args__[0] graph_type = graphql.GraphQLList(self.get_edge_field(inner.__origin__, wrapped)) else: graph_type = self.map_type(_type) if field.required: graph_type = graphql.GraphQLNonNull(graph_type) arguments = self.arguments(field, self.camelcase) field_name = field.name or name if self.camelcase: field_name = snake_to_camel(field_name, upper=False) fields[field_name] = graphql.GraphQLField(graph_type, description=field.description, args=arguments) type_name = f'{wrapped.__name__}Connection' if type_name in self.types: return self.types.get(type_name) result = graphql.GraphQLObjectType(type_name, fields=fields, interfaces=(self.types.get('Connection'),)) self.types[type_name] = result return result def get_edge_field(self, edge_type, inner: Type[T], camelcase=True): fields = dict() for name, _type in get_type_hints(edge_type).items(): field = getattr(edge_type, name, None) if not isinstance(field, Field): continue if _type is Node[T]: graph_type = self.get_node_fields(inner) else: graph_type = self.map_type(_type) if field.required: graph_type = graphql.GraphQLNonNull(graph_type) field_name = field.name or name if camelcase: field_name = snake_to_camel(field_name, upper=False) fields[field_name] = graph_type type_name = f'{inner.__name__}Edge' if type_name in self.types: return self.types.get(type_name) result = graphql.GraphQLNonNull(graphql.GraphQLObjectType( f'{inner.__name__}Edge', fields=fields, interfaces=(self.types.get('Edge'),) )) self.types[type_name] = result return result def get_node_fields(self, _type: Type[T]): type_name = f'{_type.__name__}Node' if type_name in self.types: return self.types.get(type_name) result = graphql.GraphQLObjectType( type_name, description=_type.__doc__, fields=self.get_fields(_type), interfaces=(self.types.get('Node'),) ) self.types[type_name] = result return result def build_connection_interface(self): if 'Node' not in self.types: self.types['Node'] = graphql.GraphQLInterfaceType('Node', self.get_fields(Node)) if 'Edge' not in self.types: self.types['Edge'] = graphql.GraphQLInterfaceType('Edge', self.get_fields(Edge)) if 'PageInfo' not in self.types: self.types['PageInfo'] = graphql.GraphQLObjectType('PageInfo', self.get_fields(PageInfo)) if 'Connection' not in self.types: self.types['Connection'] = graphql.GraphQLInterfaceType('Connection', self.get_fields(Connection)) PK!`m+Uyytypegql/core/connection.pyfrom typing import TypeVar, Generic, List from .arguments import Argument from .fields import Field from .graph import Graph from .types import ID T = TypeVar('T', bound=Graph) class Node(Graph, Generic[T]): id: ID = Field() class Edge(Graph, Generic[T]): node: Node[T] = Field(description='Scalar representing your data') cursor: str = Field(description='Pagination cursor') class PageInfo(Graph): has_next: bool = Field(description='When paginating forwards, are there more items?') has_previous: bool = Field(description='When paginating backwards, are there more items?') start_cursor: str = Field(description='Pagination start cursor') end_cursor: str = Field(description='Pagination end cursor') class Connection(Graph, Generic[T]): edges: List[Edge[T]] = Field(description='Connection edges') page_info: PageInfo = Field(description='Pagination information') @classmethod def page_arguments(cls): return [ Argument[int]('first', description='Retrieve only the first `n` nodes of this connection'), Argument[int]('last', description='Retrieve only the last `n` nodes of this connection'), Argument[str]('before', description='Retrieve nodes for this connection before this cursor'), Argument[str]('after', description='Retrieve nodes for this connection after this cursor') ] PK!0__typegql/core/execution.pyfrom inspect import isawaitable from typing import List, Any, Union from graphql import ExecutionContext, GraphQLField, FieldNode, GraphQLFieldResolver, GraphQLResolveInfo, GraphQLError, \ is_introspection_type from graphql.execution.values import get_argument_values from graphql.pyutils import camel_to_snake class TGQLExecutionContext(ExecutionContext): async def await_result(self, result): return await result def resolve_field_value_or_error( self, field_def: GraphQLField, field_nodes: List[FieldNode], resolve_fn: GraphQLFieldResolver, source: Any, info: GraphQLResolveInfo ) -> Union[Exception, Any]: try: camelcase = getattr(info.schema, 'camelcase', False) arguments = get_argument_values(field_def, field_nodes[0], self.variable_values) if camelcase and not is_introspection_type(info.parent_type): self.to_snake(info, arguments) result = resolve_fn(source, info, **arguments) if isawaitable(result): return self.await_result(result) return result except GraphQLError as e: return e except Exception as e: return e def to_snake(self, info, arguments): if not isinstance(arguments, dict): return keys = [k for k in arguments.keys()] for key in keys: if isinstance(arguments[key], list): for arg in arguments[key]: self.to_snake(info, arg) arguments[camel_to_snake(key)] = arguments.pop(key) PK!xr;typegql/core/fields.pyfrom typing import List, Callable, Any, Union from .arguments import Argument class _MissingType: pass MISSING = _MissingType() class Field: def __init__(self, *, name: str = '', description: str = '', required: bool = True, mutation: bool = True, arguments: List[Argument] = None, default: Union[Any, Callable] = MISSING): self.name = name self.description = description self.required = required self.mutation = mutation self.arguments = arguments or list() assert isinstance(self.arguments, (tuple, list)), 'Arguments must be a list or tuple' self.default = default class OptionalField(Field): """GraphQL field""" def __init__(self, **kwargs): super().__init__(required=False, **kwargs) class ReadonlyField(Field): """GraphQL field that can't be used in mutations""" def __init__(self, **kwargs): super().__init__(mutation=False, **kwargs) PK!NxEtypegql/core/graph.pyclass Graph: pass PK! Qtypegql/core/schema.pyimport logging from typing import Type, Any, Dict, Callable from graphql import GraphQLSchema, GraphQLObjectType, graphql, OperationType, validate_schema, GraphQLType, \ GraphQLResolveInfo from graphql.pyutils import camel_to_snake from .builder import SchemaBuilder from .execution import TGQLExecutionContext from .utils import is_graph, is_connection logger = logging.getLogger(__name__) class Schema(GraphQLSchema): def __init__(self, query: Type[Any] = None, mutation: Type[Any] = None, subscription: Type[Any] = None, types: Dict[str, GraphQLType] = None, camelcase=True): super().__init__() self.camelcase = camelcase builder = SchemaBuilder(self.camelcase, types=types) if query: self.query: Type = query query_fields = builder.get_fields(query) query = GraphQLObjectType( 'Query', fields=query_fields, ) if mutation: self.mutation: Type = mutation mutation_fields = builder.get_fields(mutation) mutation = GraphQLObjectType( 'Mutation', fields=mutation_fields ) if subscription: self.subscription: object = subscription subscription_fields = builder.get_fields(subscription) subscription = GraphQLObjectType( 'Subscription', fields=subscription_fields ) super().__init__(query, mutation, subscription) errors = validate_schema(self) if errors: raise errors[0] def _field_resolver(self, source, info, **kwargs): field_name = info.field_name if self.camelcase: field_name = camel_to_snake(field_name) if info.operation.operation == OperationType.MUTATION and is_graph(source.__class__): try: mutation = getattr(source, f'mutate_{field_name}') return mutation(info, **kwargs) except AttributeError: return if is_graph(source.__class__): _type = source.__annotations__.get(field_name) if is_connection(_type): method = getattr(_type, 'resolve', None) if method: return method(source, field_name, _type.__args__[0], info, **kwargs) value = ( source.get(field_name) if isinstance(source, dict) else getattr(source, f'resolve_{field_name}', getattr(source, field_name, None)) ) if callable(value): return value(info, **kwargs) return value async def run(self, query: str, root: Any = None, resolver: Callable[[Any, GraphQLResolveInfo, Dict[str, Any]], Any] = None, operation: str = None, context: Any = None, variables=None, middleware=None, execution_context_class=TGQLExecutionContext): if query.startswith('mutation') and not root: root = self.mutation() elif not root: root = self.query() result = await graphql(self, query, root_value=root, field_resolver=resolver or self._field_resolver, operation_name=operation, context_value=context, variable_values=variables, middleware=middleware, execution_context_class=execution_context_class) return result PK!XBtypegql/core/types.pyimport base64 from datetime import datetime from typing import Dict import graphql from graphql.language import ast class DateTime(graphql.GraphQLScalarType): def __init__(self, name='DateTime'): super().__init__( name=name, description='The `DateTime` scalar type represents a DateTime value as specified by ' '[iso8601](https://en.wikipedia.org/wiki/ISO_8601).', serialize=DateTime.serialize, parse_value=DateTime.parse_value, parse_literal=DateTime.parse_literal, ) @staticmethod def serialize(value: datetime): assert isinstance(value, datetime), 'datetime value expected' return value.isoformat() @staticmethod def parse_literal(node): if isinstance(node, ast.StringValueNode): try: return datetime.fromisoformat(node.value) except ValueError: pass @staticmethod def parse_value(value: str): try: return datetime.fromisoformat(value) except ValueError: pass class ID(graphql.GraphQLScalarType): @classmethod def encode(cls, value): if not isinstance(value, str): value = str(value) return base64.b64encode(value.encode()).decode() @classmethod def decode(cls, value): return base64.b64decode(value).decode() class Dictionary(graphql.GraphQLScalarType): def __init__(self, name='Dictionary'): super().__init__( name=name, description='Dictionary type / HashMap', serialize=Dictionary.serialize, parse_value=Dictionary.parse_value, parse_literal=Dictionary.parse_literal, ) @staticmethod def serialize(value: Dict): assert isinstance(value, dict), 'dict value expected' return value @staticmethod def parse_literal(node): if isinstance(node, ast.StringValueNode): try: return eval(node.value) except ValueError: pass @staticmethod def parse_value(value: str): try: return eval(value) except ValueError: pass PK!m=.typegql/core/utils.pyfrom enum import Enum from typing import Any, List from .graph import Graph from .connection import Connection def is_list(_type: Any) -> bool: try: return issubclass(_type.__origin__, List) except AttributeError: return False def is_enum(_type: Any) -> bool: try: return issubclass(_type, Enum) except TypeError: return False def is_graph(_type: Any) -> bool: try: return issubclass(_type, Graph) except TypeError: return False def is_connection(_type: Any) -> bool: try: return _type.__origin__ is Connection or issubclass(_type.__origin__, Connection) except (TypeError, AttributeError): return False PK!HnHTUtypegql-2.0.1.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!H6 K(  typegql-2.0.1.dist-info/METADATAYO8=ʼnkAm +m-, 'jM$vJ;giҖtPCeJs)"&<~9Y,}ȂL^yN2"̥ -_F$S3̩!9g9Ȝ EJHH,E\*D$, Yp3 T/E< GC~ASmnL 54e>y8Je>1| Ö %~zUg7㲗2ui *"g5Ahg}dpJ;"nCwcxeTk> xW("/iQ(|tkg%Asx\g[-o4'nqD \sf.`nD޽K@ƳRTP0lp# ۦ@S{\Qul@вGt>o]6@OmV 1T( ]*uH?*Z琤UNcVJRkQԣE9͸3У\Sz65,h7LӽuZ !/&_K{#imOU37u3OJ"tI[4+B}#l]|nR֕>GK޶Cg@fe;逗v|lkAZq!Y-Ue]{֓J^y=j-3yRCA߈'v6Vr{A/jv ZϚ{56T^v†\ ulۆ"YWҶpYbcʹ[m]Ҩz|9$hwKQ}CR>UU ;*Ui-J~ԡtWsk󰷀T =W>ۇ*W]۷˲n-naŢk眦n aN+u}F9oVyovMϓ֫M:o7W1۞vQm6L @U]Y(C$zppg>?r^&JQughcB{t`{AI]T!XǶ2ӂ޻k%P:BDL{i6`vѱ_}t[WشS2 7-U[6Q%; F FC.֕+'^ o0Z-+@t-CK2akNZ05OTf`Wer8163qdEbAbM^f(Bb ᐮ%,0 BUZLKcǠ[?oGla mH 1`tڠ7N` nΞ:M}llTw"`3JLJ Պ 8y]I_y/fQdA}ZO!RgiK'~`3