class ClientCertificatePolicyForHTTPS(PClass): """ ``ClientCertificatePolicyForHTTPS`` selects the correct client certificate and trust roots to use for interacting with the Kubernetes API server. :ivar credentials: All available client certificates. :ivar trust_roots: All available certificate authority certificates. """ credentials = pmap_field( NetLocation, TLSCredentials, ) trust_roots = pmap_field( NetLocation, pem.Certificate, ) def creatorForNetloc(self, hostname, port): """ Pick from amongst client certs and ca certs to create a proper TLS context factory. :see: ``twisted.web.iweb.IPolicyForHTTPS`` """ hostname = hostname.decode("ascii") netloc = NetLocation(host=hostname, port=port) client_cert, extra_cert_chain = pick_cert_for_twisted( netloc, self.credentials, ) trust_root = pick_trust_for_twisted(netloc, self.trust_roots) return ssl.optionsForClientTLS( # It is not necessarily the case that the certificate presented # will use this name but it is common to encounter self-signed # certificates which do use this name. There doesn't seem to be # anything in the configuration file which would tell us what the # proper name is. We'll probably need to make this configurable # at some point, I guess. u"kubernetes", clientCertificate=client_cert, trustRoot=trust_root, extraCertificateOptions=dict( # optionsForClientTLS gets mad if extraCertChain is () and # client_cert is None even though `()` clearly represents no # extra certificate chain. It wants them to both be None. # Make it so. extraCertChain=tuple(cert.original for cert in extra_cert_chain) or None, ), )
class Record(PRecord): value = pmap_field( int, int, invariant=( lambda pmap: (len(pmap) == 1, "Exactly one item required.") ) )
def pclass_field_for_type(self, required): # XXX ignores the range's pyrsistent_invariant return pmap_field( key_type=unicode, value_type=self.value_type.python_types, optional=not required, )
class GameState(PClass): location_name = field(str) world = pmap_field(str, Location) inventory = pvector_field(Thing) @property def location(self): return self.world[self.location_name]
class _ParsedRoutePath(PRecord): """ Parsed / derived components for matching a router path. """ parts = pvector_field(unicode) params = pvector_field(unicode) constraints = pmap_field(unicode, unicode) priority = field(initial=0, type=int)
class WrittenMessage(PClass): """ A L{Message} that has been logged. @ivar _logged_dict: The originally logged dictionary. """ _logged_dict = pmap_field((str, unicode), object) @property def timestamp(self): """ The Unix timestamp of when the message was logged. """ return self._logged_dict[TIMESTAMP_FIELD] @property def task_uuid(self): """ The UUID of the task in which the message was logged. """ return self._logged_dict[TASK_UUID_FIELD] @property def task_level(self): """ The L{TaskLevel} of this message appears within the task. """ return TaskLevel(level=self._logged_dict[TASK_LEVEL_FIELD]) @property def contents(self): """ A C{PMap}, the message contents without Eliot metadata. """ return ( self._logged_dict.discard(TIMESTAMP_FIELD) .discard(TASK_UUID_FIELD) .discard(TASK_LEVEL_FIELD) ) @classmethod def from_dict(cls, logged_dictionary): """ Reconstruct a L{WrittenMessage} from a logged dictionary. @param logged_dictionary: A C{PMap} representing a parsed log entry. @return: A L{WrittenMessage} for that dictionary. """ return cls(_logged_dict=logged_dictionary) def as_dict(self): """ Return the dictionary that was used to write this message. @return: A C{dict}, as might be logged by Eliot. """ return self._logged_dict
class Parser(PClass): """ Parse serialized Eliot messages into L{Task} instances. @ivar _tasks: Map from UUID to corresponding L{Task}. """ _tasks = pmap_field(unicode, Task) def add(self, message_dict): """ Update the L{Parser} with a dictionary containing a serialized Eliot message. @param message_dict: Dictionary of serialized Eliot message. @return: Tuple of (list of completed L{Task} instances, updated L{Parser}). """ uuid = message_dict[TASK_UUID_FIELD] if uuid in self._tasks: task = self._tasks[uuid] else: task = Task() task = task.add(message_dict) if task.is_complete(): parser = self.transform(["_tasks", uuid], discard) return [task], parser else: parser = self.transform(["_tasks", uuid], task) return [], parser def incomplete_tasks(self): """ @return: List of L{Task} that are not yet complete. """ return list(self._tasks.values()) @classmethod def parse_stream(cls, iterable): """ Parse a stream of messages into a stream of L{Task} instances. :param iterable: An iterable of serialized Eliot message dictionaries. :return: An iterable of parsed L{Task} instances. Remaining incomplete L{Task} will be returned when the input stream is exhausted. """ parser = Parser() for message_dict in iterable: completed, parser = parser.add(message_dict) for task in completed: yield task for task in parser.incomplete_tasks(): yield task
class ParserData(PClass): """ A record to store sample input and output which can be used to test the PackerOutputParser. ;ivar FilePath input: A file containing sample ``packer build -machine-readable`` output which will be fed into the parser. :ivar pmap output: The expected dictionary of the regional AMI values after parsing ``input``. """ input = field(type=FilePath, mandatory=True) output = pmap_field(key_type=unicode, value_type=unicode, optional=False)
class Dataset(PClass): """ A dataset in the configuration. :attr UUID primary: The node where the dataset should manifest. :attr int|None maximum_size: Size of the dataset in bytes or ``None`` if no particular size was requested. :attr UUID dataset_id: The UUID of the dataset. :attr metadata: A mapping between unicode keys and values. """ dataset_id = field(type=UUID, mandatory=True) primary = field(type=UUID, mandatory=True) maximum_size = field(type=(int, NoneType), mandatory=True) metadata = pmap_field(unicode, unicode)
class Dataset(PClass): """ A dataset in the configuration. :attr UUID primary: The node where the dataset should manifest. :attr int maximum_size: Size of new dataset, in bytes. :attr UUID dataset_id: The UUID of the dataset. :attr metadata: A mapping between unicode keys and values. :attr bool deleted: If true indicates this dataset should be deleted. """ dataset_id = field(type=UUID, mandatory=True) primary = field(type=UUID, mandatory=True) maximum_size = field(type=int, mandatory=True) deleted = field(type=bool, mandatory=True, initial=False) metadata = pmap_field(unicode, unicode)
class DatasetsConfiguration(PClass): """ Currently configured datasets. :ivar tag: The current version of the configuration, suitable for passing as conditional to operations like creation. :ivar datasets: The current ``Dataset`` on the server; maps ``UUID`` to ``Dataset``. """ tag = field(mandatory=True) datasets = pmap_field(UUID, Dataset) def __iter__(self): """ :return: Iterator over ``Dataset`` instances. """ return self.datasets.itervalues()
class PackerConfigure(PClass): """ The attributes necessary to create a custom packer configuration file from the prototype files in ``admin/installer/packer``. :ivar build_region: The AWS region to build images in. :ivar publish_regions: The AWS regions to publish the build images to. :ivar template: The prototype configuration to use as a base. One of `docker` or `flocker`. :ivar configuration_directory: The directory containing prototype configuration templates. :ivar source_ami_map: The AMI map containing base images. """ build_region = field(type=RegionConstant, mandatory=True) publish_regions = pvector_field(item_type=RegionConstant) template = field(type=unicode, mandatory=True) configuration_directory = field(type=FilePath, initial=PACKER_TEMPLATE_DIR) source_ami_map = pmap_field(key_type=RegionConstant, value_type=unicode)
class _Mismatch(PClass): """ Immutable Mismatch that also stores the mismatched object. :ivar mismatched: The object that failed to match. """ # XXX: No direct tests. # XXX: jml thinks the base testtools mismatch should be extended to # include the mismatched object. mismatched = field(object) _description = field((str, unicode)) _details = pmap_field((str, unicode), Content) def describe(self): return self._description def get_details(self): return self._details
class Record(PRecord): value = pmap_field((Something, Another), int) value2 = pmap_field(str, (int, float))
class Record(PRecord): value = pmap_field(Something, Another) value2 = pmap_field(int, float)
class Record(PRecord): value = pmap_field(int, int, optional=True)
class Record(PRecord): value = pmap_field(int, int)
class Record(PRecord): value = pmap_field(("record_test.Something", "record_test.Another"), int) value2 = pmap_field(str, ("record_test.Something", "record_test.Another"))
class SoccerState(GameState): ################################################################ ## PUBLIC METHODS ################################################################ # You may use these methods when implementing the MinimaxAgent. # For information on these methods, see _game.GameState @property def num_players(self): return len(self.players) @property def current_player(self): return self.current_player_id @property def is_terminal(self): return self.winner @property def actions(self): player = self.current_player_obj actions = [] if player.has_ball: actions += [Action.KICK] # actions += [Action.CHANGE_STANCE] dx = 1 if player.team == Team.RED else -1 actions += [ Action.move(1, 0), Action.move(-1, 0), Action.move(0, 1), Action.move(0, -1) ] if player.x <= 1: dx = 1 elif player.x >= self.pitch.width: dx = -1 else: dx = 1 if player.team == Team.RED else -1 actions += [Action.move(dx, 1), Action.move(dx, -1)] return actions def reward(self, player_id): if not self.is_terminal: return None return 10 if self.winner == self.players[player_id].team else -10 def act(self, action): player = self.current_player_obj state = self state = state._action_is_valid(action) if not state: return None if action == Action.KICK: state = self._update_kick() elif action == Action.CHANGE_STANCE: state = state.transform(self._cpk('stance'), (player.stance + 1) % 2) elif isinstance(action, tuple) and action[0] == Action.MOVE: (_, dx, dy) = action state = self._update_move_to(player.x + dx, player.y + dy) else: return None if state: state = state.set(current_player_id=(self.current_player + 1) % self.num_players) return state ################################################################ ## INTERNAL ################################################################ # These are 'internal' methods and variables to the SoccerState # class. You should not use any of these methods in the # MinimaxAgent implementation, but can use them in your soccer # evaluation function. ## Variables # Each of these fields can be directly accessed by calling # `state.<variable_name>`. # The ID of the current player. current_player_id = field(type=int) # A persistent list of players. Can be accessed like a normal # Python list: players[0] is the first player, etc. # # Each player object players[i] is a PMap (equivalent to a Python # dict) with the following structure: # # players[i] = { # type="player", # The type of object # index=i, # The index of the player in the players list # agent=agent, # The agent controlling the player # team={Team.RED|Team.BLUE} # The player's team # x=x_pos, y=y_pos, # The current (x,y) position of the player # has_ball={True,False} # True if the player currently has the ball # } players = pvector_field(PMap) # Contains the players of each team. Keys are teams['red'] and # teams['blue']. Probably should switch to using the team enum in # the future. teams = pmap_field(str, PVector) # Information about the ball. Has the following information: # # ball = { # type='ball', # The type of object # on_field={True,False} # True if nobody has the ball. # x=x_pos, y=y_pos # The (x,y) position of the ball # } # # Note that the position of the ball is updated even when a player # has the ball. ball = pmap_field(str, (bool, str, int)) # The term 'field' was ambiguous, so this is just some # information about the soccer field: # # pitch = { # width=pitch_width # The width of the soccer field # height=pitch_height # The height of the soccer field # goal_height=goal_height # The height of the goal (along the y-axis) # } pitch = pmap_field(str, int) # Indicates the winner of the round. When the game hasn't # finished, is None; when the game is complete, is the Team that # won the game. winner = field(type=(Team, type(None))) @property def objects(self): """Returns the list of 'objects' (players + the ball)""" return list(self.players) + [self.ball] def dist_to_goal(self, pos, team): """Returns the distance between an (x, y) position and a team's goal.""" goal_pos = (self.pitch.width+0.5, self.pitch.height/2) if team == Team.RED \ else (0.5, self.pitch.height/2) return math.sqrt( sum([(p1 + 0.51 - p2)**2 for p1, p2 in zip(pos, goal_pos)])) def at(self, x, y): """Returns the object at position (x,y). If no object is there, return None. """ for obj in self.objects: ## invariant: no two objects occupy the same space if obj.x == x and obj.y == y: return obj return None @property def current_player_obj(self): """Returns the info for the current player in a PMap object.""" return self.players[self.current_player] @property def player_with_ball(self): return next((p for p in self.players if p.has_ball), [None]) def goal_pos(self, team): """Returns the position of the `teams`'s goal.""" return self.red_goal_pos if team == Team.RED \ else self.blue_goal_pos @property def red_goal_pos(self): """The position of the red team's goal.""" return (self.pitch.width + 1, int(self.pitch.height / 2) + 1) @property def blue_goal_pos(self): """The position of the blue team's goal.""" return (0, int(self.pitch.height / 2) + 1) @property def goal_top(self): """Returns the y-position of the top of the goal.""" return int(self.pitch.height + self.pitch.goal_height) / 2 @property def goal_bottom(self): """Returns the y-position of the bottom of the goal.""" return int(self.pitch.height - self.pitch.goal_height) / 2 + 1 @property def ball_in_red_goal(self): """Returns true if the ball is currently in the Red goal.""" return self.ball.x > self.pitch.width and\ self.goal_bottom <= self.ball.y and self.ball.y <= self.goal_top @property def ball_in_blue_goal(self): """Returns true if the ball is currently in the Blue goal.""" return self.ball.x < 1 and\ self.goal_bottom <= self.ball.y and self.ball.y <= self.goal_top def player_in_red_penalty_area(self, player_id): """Returns true if the player_id is in the Red penalty area.""" player = self.players[player_id] return player.x >= self.pitch.width-3 and\ self.goal_bottom-1 <= player.y and player.y <= self.goal_top+1 def player_in_blue_penalty_area(self, player_id): """Returns true if the player_id is in the Blue penalty area.""" player = self.players[player_id] return player.x <= 3 and\ self.goal_bottom-1 <= player.y and player.y <= self.goal_top+1 def _cpk(self, *keys): """Internal method, returns a tuple for the current player that is used for updating the state. """ return ('players', self.current_player, *keys) def _update_move_to(self, x, y): """State update: Current player moves to pos (x,y).""" player = self.current_player_obj state = self if player.has_ball: if y < 1 or y > state.pitch.height: ## sidelines state = state._update_reset(prefer_side=player.team.inverse) elif x < 1 or x > self.pitch.width: if self.goal_bottom <= y and y <= self.goal_top: if x < 1: state = state.set(winner=Team.BLUE) else: state = state.set(winner=Team.RED) else: if x < 1 and player.team == Team.RED \ or x > self.pitch.width and player.team == Team.BLUE: state = state._update_corner_kick() else: state = state._update_reset( prefer_side=player.team.inverse) else: ## deal with collision (state, do_move) = state._update_check_collide(x, y) if do_move: state = state.transform(self._cpk('x'), x, self._cpk('y'), y, ('ball', 'x'), x, ('ball', 'y'), y) state = state._update_check_goal() else: if x < 1 or x > self.pitch.width: return None elif y < 1 or y > self.pitch.height: return None else: ## deal with collision (state, do_move) = state._update_check_collide(x, y) if do_move: state = state.transform(self._cpk('x'), x, self._cpk('y'), y) return state def _update_corner_kick(self): state = self p2 = self.player_with_ball if not p2: return state goal_pos = self.goal_pos(p2.team.inverse) p1 = self.players[self.teams[p2.team.inverse.name][0]] goal_pos_x = goal_pos[0] dx = -1 if goal_pos_x > 1 else 1 state = state.transform( ('players', p1.index, 'x'), goal_pos_x + dx, ('players', p1.index, 'y'), 1, ('players', p1.index, 'has_ball'), True, ('players', p2.index, 'x'), goal_pos_x + 3 * dx, ('players', p2.index, 'y'), 3, ('players', p2.index, 'has_ball'), False) return state def is_goal(self, dist, angle): return 20 * abs(angle)**2 / (dist**2) > 3e-1 def can_shoot_from(self, x, y, team): (x, y) = (x + 0.5, y - 0.5) goal_x = self.pitch.width + 2 if team == Team.RED else 0 goal_y1 = int(self.pitch.height - self.pitch.goal_height) / 2 goal_y2 = int(self.pitch.height + self.pitch.goal_height) / 2 dx = goal_x - x dy1 = (goal_y1 - y) dy2 = (goal_y2 - y) norm1 = math.sqrt(dx**2 + dy1**2) norm2 = math.sqrt(dx**2 + dy2**2) dy = ((goal_y1 + goal_y2) / 2 - y) dist = math.sqrt(dx**2 + dy**2) angle = math.acos((dx**2 + dy1 * dy2) / (norm1 * norm2)) return self.is_goal(dist, angle) def check_kick(self, player): (x, y) = (player.x + 0.5, player.y - 0.5) goal_x = self.pitch.width + 2 if player.team == Team.RED else 0 goal_y1 = int(self.pitch.height - self.pitch.goal_height) / 2 goal_y2 = int(self.pitch.height + self.pitch.goal_height) / 2 dx = goal_x - x dy1 = (goal_y1 - y) dy2 = (goal_y2 - y) norm1 = math.sqrt(dx**2 + dy1**2) norm2 = math.sqrt(dx**2 + dy2**2) dy = ((goal_y1 + goal_y2) / 2 - y) f_y1 = lambda obj_x: y + dy1 * obj_x f_y2 = lambda obj_x: y + dy2 * obj_x dist = math.sqrt(dx**2 + dy**2) angle = math.acos((dx**2 + dy1 * dy2) / (norm1 * norm2)) # Check for interceptions intercept = (None, float("inf")) for obj in self.players: if obj.index == player.index: continue (obj_x, obj_y) = (obj.x + 0.5, obj.y + 0.5) obj_x = (obj_x - x) / dx if obj_y >= f_y1(obj_x) and obj_y <= f_y2(obj_x): new_i = (obj, math.sqrt((x - obj_x)**2 + (y - obj_y)**2)) intercept = min([intercept, new_i], key=lambda x: x[1]) return (dist, angle, self.is_goal(dist, angle), intercept[0]) def _update_kick(self): """State update: Current player kicks towards opponent goal.""" player = self.current_player_obj state = self (_, _, is_goal, intercept_player) = self.check_kick(player) if intercept_player: state = state._update_switch_possession(player.index, intercept_player.index) elif is_goal: goal_pos = self.goal_pos(player.team) state = state.transform(('ball', 'x'), goal_pos[0], ('ball', 'y'), goal_pos[1], ('ball', 'on_field'), True, self._cpk('has_ball'), False) state = state._update_check_goal() else: state = state._update_reset(prefer_side=player.team.inverse) return state def _update_switch_possession(self, player_a, player_b): """State update: Possession of ball switches between player_a and player_b """ state = self if self.players[player_a].has_ball: p1 = self.players[player_a] p2 = self.players[player_b] else: p1 = self.players[player_b] p2 = self.players[player_a] goal_pos = self.goal_pos(p1.team.inverse) state = state.transform(('players', p1.index, 'has_ball'), False, ('players', p2.index, 'has_ball'), True, ('ball', 'x'), p2.x, ('ball', 'y'), p2.y) state = state._update_place_between(p1.index, p2.x, p2.y, goal_pos[0], goal_pos[1]) return state def _update_place_between(self, player_id, x1, y1, x2, y2): """State update: player_id is placed in between position (x1, y1) and (x2, y2) """ x = int((x1 + x2) / 2) y = int((y1 + y2) / 2) state = self if state.at(x, y): if not state.at(x + 1, y) and x + 1 <= self.pitch.width: x += 1 elif not state.at(x - 1, y) and x - 1 >= 1: x -= 1 elif not state.at(x, y + 1) and y + 1 <= self.pitch.height: y += 1 elif not state.at(x, y - 1) and y - 1 >= 1: y -= 1 state = state.transform(('players', player_id, 'x'), x, ('players', player_id, 'y'), y) return state def _update_reset(self, prefer_side=None, random_pos=False): """State update: Players and ball are reset to original position. :param Team prefer_side: If set to a Team, that team will receive the ball when the game is reset. :param bool random_pos: If True, players will be randomly placed on their side of the field. """ state = self mid_x = int(self.pitch.width / 2) + 1 mid_y = int(self.pitch.height / 2) + 1 ball_offset = 0 if not prefer_side \ else (-1 if prefer_side == 'red' else 1) if not prefer_side: state = state.transform(('ball', 'x'), mid_x + ball_offset, ('ball', 'y'), mid_y, ('ball', 'on_field'), True) else: state = state.transform(('ball', 'on_field'), False) for team_name in ('red', 'blue'): team = self.teams[team_name] i = 0 for dx in range(4): done = False for dy in range(5): if not random_pos: x = mid_x + (-dx - 5 if team_name == 'red' else dx + 5) y = mid_y + (2 * int(dy / 2) * (-1 if dy % 2 else 1)) state = state\ .transform(('players', team[i], 'x'), x)\ .transform(('players', team[i], 'y'), y)\ .transform(('players', team[i], 'has_ball'), False) if i == 0 and prefer_side == self.players[ team[i]].team: state = state.transform( ('players', team[i], 'has_ball'), True, ('ball', 'x'), x, ('ball', 'y'), y) i += 1 else: x_range = range(1,mid_x) if team_name == 'red' \ else range(mid_x+1,self.pitch.width) y_range = range(1, self.pitch.height) state = state\ .transform(('players', team[i], 'x'), random.choice(x_range))\ .transform(('players', team[i], 'y'), random.choice(y_range)) if i >= len(team): done = True break if done: break return state def _update_check_collide(self, x, y): """State update: Check if there will be a collision between the current player and whatever is at position (x,y). """ state = self do_move = True obj = state.at(x, y) if not obj: do_move = True elif obj.type == 'ball': ## If we collide with the ball... ## Pick up the ball state = state.transform(('ball', 'x'), x, ('ball', 'y'), y, ('ball', 'on_field'), False, self._cpk('has_ball'), True) do_move = True elif obj.type == 'player': # state = state.transform( # ('ball', 'x'), x, ('ball', 'y'), y, # self._cpk('has_ball'), False, # ('players', obj.index, 'has_ball'), True # ) if self.current_player_obj.has_ball or obj.has_ball: state = state._update_switch_possession( self.current_player, obj.index) do_move = False else: state = None do_move = False return (state, do_move) def _update_check_goal(self): """State update: Check if the ball is in a goal, and, if so, update the winner of the game. """ state = self if self.ball_in_red_goal: state = state.set(winner=Team.RED) elif self.ball_in_blue_goal: state = state.set(winner=Team.BLUE) return state def draw(self): """Internal method, draws the current game configuration.""" BLOCK_SIZE = B = 32 PITCH_COLOR = (0, 200, 0) PLAYER_RED_COLOR = (255, 0, 0) PLAYER_BLUE_COLOR = (0, 0, 255) BALL_COLOR = (255, 255, 255) surf = pygame.Surface( ((self.pitch.width + 2) * B, (self.pitch.height + 2) * B)) (W, H) = (surf.get_width(), surf.get_height()) (W_p, H_p) = (self.pitch.width, self.pitch.height) H_g = self.pitch.goal_height surf.fill(PITCH_COLOR) # Draw grid for i in range(self.pitch.width + 2): for j in range(self.pitch.height + 2): s1 = surf.subsurface( (i * B, (self.pitch.height - j + 1) * B, B, B)) if 1 <= i and i <= self.pitch.width and\ 1 <= j and j <= self.pitch.height: if self.can_shoot_from(i, j, Team.RED) or self.can_shoot_from( i, j, Team.BLUE): s1.fill((100, 255, 100), (1, 1, B - 2, B - 2)) else: s1.fill((0, 255, 0), (1, 1, B - 2, B - 2)) # Draw field lines pygame.draw.lines(surf, (255, 255, 255), True, [(B, B), (W - B, B), (W - B, H - B), (B, H - B)], 3) pygame.draw.line(surf, (255, 255, 255), (int(W / 2), B), (int(W / 2), H - B), 3) pygame.draw.line(surf, (255, 255, 255), (int(W / 2), B), (int(W / 2), H - B), 3) pygame.draw.line(surf, (255, 255, 255), (int(W / 2), B), (int(W / 2), H - B), 3) pygame.draw.circle(surf, (255, 255, 255), (int(W / 2), int(H / 2)), int(B * 2.5), 3) pygame.draw.lines(surf, (255, 255, 255), False, [(B, B * (int((H_p + H_g) / 2) + 2)), (4 * B, B * (int((H_p + H_g) / 2) + 2)), (4 * B, B * (int((H_p - H_g) / 2))), (B, B * (int((H_p - H_g) / 2)))], 3) pygame.draw.lines(surf, (255, 255, 255), False, [(W - B, B * (int((H_p + H_g) / 2) + 2)), (W - 4 * B, B * (int((H_p + H_g) / 2) + 2)), (W - 4 * B, B * (int((H_p - H_g) / 2))), (W - B, B * (int((H_p - H_g) / 2)))], 3) # pygame.draw.circle(surf, (255, 255, 255), (W-B, int(H/2)), B*3, 3) # surf.fill(PITCH_COLOR, (0, 0, B-1, H)) # surf.fill(PITCH_COLOR, (W-B+2, 0, B, H)) font = pygame.font.SysFont("monospace", 18) font.set_bold(True) for i in range(self.pitch.width + 2): for j in range(self.pitch.height + 2): obj = self.at(i, j) s1 = surf.subsurface( (i * B, (self.pitch.height - j + 1) * B, B, B)) if obj and obj.type == 'ball' and obj.on_field: s1.fill(BALL_COLOR, (6, 6, B - 12, B - 12)) elif obj and obj.type == 'player': c = PLAYER_RED_COLOR if obj.team == Team.RED else PLAYER_BLUE_COLOR if obj.index == self.current_player: s1.fill((250, 250, 0)) s1.fill(c, (2, 2, B - 4, B - 4)) if obj.has_ball: pygame.draw.rect(s1, BALL_COLOR, (6, 6, B - 12, B - 12)) # label = font.render("R" if obj.stance == 0 else "B", 1, (0, 255, 0)) # s1.blit(label, (B/4, B/4)) if (i < 1 or i > self.pitch.width) and \ (self.goal_bottom <= j and j <= self.goal_top): for l in range(4, BLOCK_SIZE, 8): pygame.draw.line(s1, (255, 255, 255), (0, l), (B, l)) pygame.draw.line(s1, (255, 255, 255), (l, 0), (l, B)) GOAL_TOP = BLOCK_SIZE * (1 + int( (self.pitch.height + self.pitch.goal_height) / 2)) GOAL_BOTTOM = BLOCK_SIZE * (1 + int( (self.pitch.height - self.pitch.goal_height) / 2)) pygame.draw.lines(surf, (0, 0, 0), False, [(0, GOAL_TOP), (B, GOAL_TOP), (B, GOAL_BOTTOM), (0, GOAL_BOTTOM)], 3) pygame.draw.lines(surf, (0, 0, 0), False, [(W, GOAL_TOP), (W - B, GOAL_TOP), (W - B, GOAL_BOTTOM), (W, GOAL_BOTTOM)], 3) pygame.draw.rect(surf, PLAYER_RED_COLOR, (0, 0, 6, surf.get_height())) pygame.draw.rect(surf, PLAYER_BLUE_COLOR, (surf.get_width() - 7, 0, 6, surf.get_height())) if self.is_terminal: winner_rect = ((1 + self.pitch.width / 2 - 3) * B, (self.pitch.height / 2 + 2) * B, 6 * B, 2 * B) border_rect = (winner_rect[0] - 4, winner_rect[1] - 4, winner_rect[2] + 8, winner_rect[3] + 8) label = None if self.winner == Team.RED: label = font.render("Team RED wins!", 1, (255, 255, 255)) pygame.draw.rect(surf, (200, 100, 100), border_rect) pygame.draw.rect(surf, (255, 0, 0), winner_rect) else: label = font.render("Team BLUE wins!", 1, (255, 255, 255)) pygame.draw.rect(surf, (100, 100, 200), border_rect) pygame.draw.rect(surf, (0, 0, 255), winner_rect) surf.blit(label, (winner_rect[0] + 7, winner_rect[1] + int(B / 2) + 4)) return surf def __eq__(self, other): return hash(self) == hash(other) def __hash__(self): key = [self.current_player, (self.ball.x, self.ball.y)] \ + [(p.x, p.y, p.stance, p.has_ball) for p in self.players] return hash(tuple(key))
class TaggedUnionInvariant(PClass): """ An invariant that ensure the given object has an allowd tag attribute, and that all the other specified attributes are present if and only if the object has the appropriate tag. The tags must be :py:class:`NamedConstant`s. .. note:: Attributes that aren't specified by any tag are ignored. :param str tag_attribute: The attribute that contains the tag. :param dict attributes_for_tag: Dictionary mapping tags to the set of attributes allowed by that tag. """ tag_attribute = field(str, mandatory=True) attributes_for_tag = pmap_field( key_type=NamedConstant, value_type=_AttributeSet, optional=True, ) @property def _allowed_tags(self): """ The set of all allowed tags. """ return pset(self.attributes_for_tag.keys()) @property def _all_attributes(self): """ The set of all attributes controlled by the invariant. """ return pset({ attribute for attributes in self.attributes_for_tag.values() for attribute in attributes }) def __call__(self, value): """ Check that the invariant holds for the given value. :param value: Value to check invariant for. :returns: Pair of whether the invariant holds, and a message describing why it doesn't. :rtype: `tuple` of `bool` and `str` """ tag = getattr(value, self.tag_attribute) if tag not in self._allowed_tags: return (False, "can only be in {tag_name}s {tags}.".format( tag_name=self.tag_attribute, tags=', '.join(map("`{0.name}`".format, self._allowed_tags)), )) for attribute in self.attributes_for_tag[tag]: if not hasattr(value, attribute): return ( False, "`{attr}` must be specified in {tag_name} `{tag}`".format( attr=attribute, tag_name=self.tag_attribute, tag=tag.name)) for attribute in self._all_attributes - self.attributes_for_tag[tag]: if hasattr(value, attribute): return ( False, "`{attr}` can't be specified in {tag_name} `{tag}`".format( attr=attribute, tag_name=self.tag_attribute, tag=tag.name)) return (True, "")
class MyClass1(PClass): f = pmap_field(key_type=Foo, value_type=int)
class TypedContainerObj(PClass): map = pmap_field(str, str) set = pset_field(str) vec = pvector_field(str)
class WrittenAction(PClass): """ An Action that has been logged. This class is intended to provide a definition within Eliot of what an action actually is, and a means of constructing actions that are known to be valid. @ivar WrittenMessage start_message: A start message whose task UUID and level match this action, or C{None} if it is not yet set on the action. @ivar WrittenMessage end_message: An end message hose task UUID and level match this action. Can be C{None} if the action is unfinished. @ivar TaskLevel task_level: The action's task level, e.g. if start message has level C{[2, 3, 1]} it will be C{TaskLevel(level=[2, 3])}. @ivar UUID task_uuid: The UUID of the task to which this action belongs. @ivar _children: A L{pmap} from L{TaskLevel} to the L{WrittenAction} and L{WrittenMessage} objects that make up this action. """ start_message = field(type=optional(WrittenMessage), mandatory=True, initial=None) end_message = field(type=optional(WrittenMessage), mandatory=True, initial=None) task_level = field(type=TaskLevel, mandatory=True) task_uuid = field(type=unicode, mandatory=True, factory=unicode) # Pyrsistent doesn't support pmap_field with recursive types. _children = pmap_field(TaskLevel, object) @classmethod def from_messages(cls, start_message=None, children=pvector(), end_message=None): """ Create a C{WrittenAction} from C{WrittenMessage}s and other C{WrittenAction}s. @param WrittenMessage start_message: A message that has C{ACTION_STATUS_FIELD}, C{ACTION_TYPE_FIELD}, and a C{task_level} that ends in C{1}, or C{None} if unavailable. @param children: An iterable of C{WrittenMessage} and C{WrittenAction} @param WrittenMessage end_message: A message that has the same C{action_type} as this action. @raise WrongTask: If C{end_message} has a C{task_uuid} that differs from C{start_message.task_uuid}. @raise WrongTaskLevel: If any child message or C{end_message} has a C{task_level} that means it is not a direct child. @raise WrongActionType: If C{end_message} has an C{ACTION_TYPE_FIELD} that differs from the C{ACTION_TYPE_FIELD} of C{start_message}. @raise InvalidStatus: If C{end_message} doesn't have an C{action_status}, or has one that is not C{SUCCEEDED_STATUS} or C{FAILED_STATUS}. @raise InvalidStartMessage: If C{start_message} does not have a C{ACTION_STATUS_FIELD} of C{STARTED_STATUS}, or if it has a C{task_level} indicating that it is not the first message of an action. @return: A new C{WrittenAction}. """ actual_message = [ message for message in [start_message, end_message] + list(children) if message ][0] action = cls( task_level=actual_message.task_level.parent(), task_uuid=actual_message.task_uuid, ) if start_message: action = action._start(start_message) for child in children: if action._children.get(child.task_level, child) != child: raise DuplicateChild(action, child) action = action._add_child(child) if end_message: action = action._end(end_message) return action @property def action_type(self): """ The type of this action, e.g. C{"yourapp:subsystem:dosomething"}. """ if self.start_message: return self.start_message.contents[ACTION_TYPE_FIELD] elif self.end_message: return self.end_message.contents[ACTION_TYPE_FIELD] else: return None @property def status(self): """ One of C{STARTED_STATUS}, C{SUCCEEDED_STATUS}, C{FAILED_STATUS} or C{None}. """ message = self.end_message if self.end_message else self.start_message if message: return message.contents[ACTION_STATUS_FIELD] else: return None @property def start_time(self): """ The Unix timestamp of when the action started, or C{None} if there has been no start message added so far. """ if self.start_message: return self.start_message.timestamp @property def end_time(self): """ The Unix timestamp of when the action ended, or C{None} if there has been no end message. """ if self.end_message: return self.end_message.timestamp @property def exception(self): """ If the action failed, the name of the exception that was raised to cause it to fail. If the action succeeded, or hasn't finished yet, then C{None}. """ if self.end_message: return self.end_message.contents.get(EXCEPTION_FIELD, None) @property def reason(self): """ The reason the action failed. If the action succeeded, or hasn't finished yet, then C{None}. """ if self.end_message: return self.end_message.contents.get(REASON_FIELD, None) @property def children(self): """ The list of child messages and actions sorted by task level, excluding the start and end messages. """ return pvector( sorted(self._children.values(), key=lambda m: m.task_level)) def _validate_message(self, message): """ Is C{message} a valid direct child of this action? @param message: Either a C{WrittenAction} or a C{WrittenMessage}. @raise WrongTask: If C{message} has a C{task_uuid} that differs from the action's C{task_uuid}. @raise WrongTaskLevel: If C{message} has a C{task_level} that means it's not a direct child. """ if message.task_uuid != self.task_uuid: raise WrongTask(self, message) if not message.task_level.parent() == self.task_level: raise WrongTaskLevel(self, message) def _add_child(self, message): """ Return a new action with C{message} added as a child. Assumes C{message} is not an end message. @param message: Either a C{WrittenAction} or a C{WrittenMessage}. @raise WrongTask: If C{message} has a C{task_uuid} that differs from the action's C{task_uuid}. @raise WrongTaskLevel: If C{message} has a C{task_level} that means it's not a direct child. @return: A new C{WrittenAction}. """ self._validate_message(message) level = message.task_level return self.transform(("_children", level), message) def _start(self, start_message): """ Start this action given its start message. @param WrittenMessage start_message: A start message that has the same level as this action. @raise InvalidStartMessage: If C{start_message} does not have a C{ACTION_STATUS_FIELD} of C{STARTED_STATUS}, or if it has a C{task_level} indicating that it is not the first message of an action. """ if start_message.contents.get(ACTION_STATUS_FIELD, None) != STARTED_STATUS: raise InvalidStartMessage.wrong_status(start_message) if start_message.task_level.level[-1] != 1: raise InvalidStartMessage.wrong_task_level(start_message) return self.set(start_message=start_message) def _end(self, end_message): """ End this action with C{end_message}. Assumes that the action has not already been ended. @param WrittenMessage end_message: An end message that has the same level as this action. @raise WrongTask: If C{end_message} has a C{task_uuid} that differs from the action's C{task_uuid}. @raise WrongTaskLevel: If C{end_message} has a C{task_level} that means it's not a direct child. @raise InvalidStatus: If C{end_message} doesn't have an C{action_status}, or has one that is not C{SUCCEEDED_STATUS} or C{FAILED_STATUS}. @return: A new, completed C{WrittenAction}. """ action_type = end_message.contents.get(ACTION_TYPE_FIELD, None) if self.action_type not in (None, action_type): raise WrongActionType(self, end_message) self._validate_message(end_message) status = end_message.contents.get(ACTION_STATUS_FIELD, None) if status not in (FAILED_STATUS, SUCCEEDED_STATUS): raise InvalidStatus(self, end_message) return self.set(end_message=end_message)
class Bar(PRecord): bar = pmap_field(str, Foo)
class Task(PClass): """ A tree of actions with the same task UUID. """ _nodes = pmap_field(TaskLevel, (WrittenAction, WrittenMessage)) _completed = pset_field(TaskLevel) _root_level = TaskLevel(level=[]) def root(self): """ @return: The root L{WrittenAction}. """ return self._nodes[self._root_level] def is_complete(self): """ @return bool: True only if all messages in the task tree have been added to it. """ return self._root_level in self._completed def _insert_action(self, node): """ Add a L{WrittenAction} to the tree. Parent actions will be created as necessary. @param child: A L{WrittenAction} to add to the tree. @return: Updated L{Task}. """ task = self if ( node.end_message and node.start_message and (len(node.children) == node.end_message.task_level.level[-1] - 2)): # Possibly this action is complete, make sure all sub-actions # are complete: completed = True for child in node.children: if ( isinstance(child, WrittenAction) and child.task_level not in self._completed): completed = False break if completed: task = task.transform(["_completed"], lambda s: s.add(node.task_level)) task = task.transform(["_nodes", node.task_level], node) return task._ensure_node_parents(node) def _ensure_node_parents(self, child): """ Ensure the node (WrittenAction/WrittenMessage) is referenced by parent nodes. Parent actions will be created as necessary. @param child: A L{WrittenMessage} or L{WrittenAction} which is being added to the tree. @return: Updated L{Task}. """ task_level = child.task_level if task_level.parent() is None: return self parent = self._nodes.get(task_level.parent()) if parent is None: parent = WrittenAction( task_level=task_level.parent(), task_uuid=child.task_uuid) parent = parent._add_child(child) return self._insert_action(parent) def add(self, message_dict): """ Update the L{Task} with a dictionary containing a serialized Eliot message. @param message_dict: Dictionary whose task UUID matches this one. @return: Updated L{Task}. """ is_action = message_dict.get(ACTION_TYPE_FIELD) is not None written_message = WrittenMessage.from_dict(message_dict) if is_action: action_level = written_message.task_level.parent() action = self._nodes.get(action_level) if action is None: action = WrittenAction( task_level=action_level, task_uuid=message_dict[TASK_UUID_FIELD]) if message_dict[ACTION_STATUS_FIELD] == STARTED_STATUS: # Either newly created MissingAction, or one created by # previously added descendant of the action. action = action._start(written_message) else: action = action._end(written_message) return self._insert_action(action) else: # Special case where there is no action: if written_message.task_level.level == [1]: return self.transform([ "_nodes", self._root_level], written_message, [ "_completed"], lambda s: s.add(self._root_level)) else: return self._ensure_node_parents(written_message)
class RecordContainingContainers(PRecord): map = pmap_field(str, str) vec = pvector_field(str) set = pset_field(str)
class Record(PRecord): value = pmap_field((int, str), type(None))
class Record(PRecord): value = pmap_field(int, (str, type(None)))
class MyClass2(PClass): f = pmap_field(key_type=(Foo, ), value_type=int)
class Record(PRecord): value = pmap_field("record_test.Something", "record_test.Another")