<commit_before># Copyright 2020 DeepMind Technologies Limited.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A simple human player for testing a Melting Pot level.

Use `WASD` keys to move the character around.
Use `Q and E` to turn the character.
Use `SPACE` to fire the zapper.
Use `TAB` to switch between players.
"""

import collections
import enum

from typing import Any, Callable, Dict, Mapping, Optional, Sequence, Tuple
import dm_env

from ml_collections import config_dict
import numpy as np
import pygame

import dmlab2d
from meltingpot.python.utils.substrates import builder

WHITE = (255, 255, 255)

MOVEMENT_MAP = {
    'NONE': 0,
    'FORWARD': 1,
    'RIGHT': 2,
    'BACKWARD': 3,
    'LEFT': 4,
}

EnvBuilder = Callable[..., dmlab2d.Environment]  # Only supporting kwargs.

ActionMap = Mapping[str, Callable[[], int]]

class RenderType(enum.Enum):
  NONE = 0
  PYGAME = 1

def get_random_direction() -> int:
  """Gets a random direction."""
  return np.random.choice(list(MOVEMENT_MAP.values()))

def get_random_turn() -> int:
  """Gets a random turn."""
  return np.random.choice([-1, 0, 1])

def get_random_fire() -> int:
  """Gets a random fire."""
  return np.random.choice([0, 1])

def get_direction_pressed() -> int:
  """Gets direction pressed."""
  key_pressed = pygame.key.get_pressed()
  if key_pressed[pygame.K_UP] or key_pressed[pygame.K_w]:
    return MOVEMENT_MAP['FORWARD']
  if key_pressed[pygame.K_RIGHT] or key_pressed[pygame.K_d]:
    return MOVEMENT_MAP['RIGHT']
  if key_pressed[pygame.K_DOWN] or key_pressed[pygame.K_s]:
    return MOVEMENT_MAP['BACKWARD']
  if key_pressed[pygame.K_LEFT] or key_pressed[pygame.K_a]:
    return MOVEMENT_MAP['LEFT']
  return MOVEMENT_MAP['NONE']

def get_turn_pressed() -> int:
  """Calculates turn increment."""
  key_pressed = pygame.key.get_pressed()
  if key_pressed[pygame.K_DELETE] or key_pressed[pygame.K_q]:
    return -1
  if key_pressed[pygame.K_PAGEDOWN] or key_pressed[pygame.K_e]:
    return 1
  return 0

def get_space_key_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_SPACE] else 0

def get_key_number_pressed() -> int:
  number_keys = [pygame.K_0, pygame.K_1, pygame.K_2, pygame.K_3, pygame.K_4,
                 pygame.K_5, pygame.K_6, pygame.K_7, pygame.K_8, pygame.K_9]
  for num in range(len(number_keys)):
    if pygame.key.get_pressed()[number_keys[num]]:
      return num
  return -1

def get_key_number_one_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_1] else 0

def get_key_number_two_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_2] else 0

def get_key_number_three_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_3] else 0

def get_key_number_four_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_4] else 0

def get_key_number_five_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_5] else 0

def get_left_control_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_LCTRL] else 0

def get_left_shift_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_LSHIFT] else 0

def get_right_shift_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_RSHIFT] else 0

def get_key_c_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_c] else 0

def get_key_z_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_z] else 0

def get_key_x_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_x] else 0

def _split_key(key: str) -> Tuple[str, str]:
  """Splits the key into player index and name."""
  return tuple(key.split('.', maxsplit=1))

def _get_rewards(timestep: dm_env.TimeStep) -> Mapping[str, float]:
  """Gets the list of rewards, one for each player."""
  rewards = {}
  for key in timestep.observation.keys():
    if key.endswith('.REWARD'):
      player_prefix, name = _split_key(key)
      if name == 'REWARD':
        rewards[player_prefix] = timestep.observation[key]
  return rewards

class ActionReader(object):
  """Convert keyboard actions to environment actions."""

  def __init__(self, env: dmlab2d.Environment, action_map: ActionMap):
    # Actions are named "<player_prefix>.<action_name>"
    self._action_map = action_map
    self._action_spec = env.action_spec()
    assert isinstance(self._action_spec, dict)
    self._action_names = set()
    for action_key in self._action_spec.keys():
      _, action_name = _split_key(action_key)
      self._action_names.add(action_name)

  def step(self, player_prefix: str) -> Sequence[Sequence[Mapping[str, int]]]:
    """Update the actions of player `player_prefix`."""
    actions = {action_key: 0 for action_key in self._action_spec.keys()}
    for action_name in self._action_names:
      actions[f'{player_prefix}.{action_name}'] = self._action_map[
          action_name]()
    return actions

def run_episode(
    render_observation: str,
    config_overrides: Dict[str, Any],
    action_map: ActionMap,
    full_config: config_dict.ConfigDict,
    interactive: RenderType = RenderType.PYGAME,
    screen_width: int = 800,
    screen_height: int = 600,
    fps: int = 8,
    verbose_fn: Optional[Callable[[dm_env.TimeStep, int], None]] = None,
    text_display_fn: Optional[Callable[[dm_env.TimeStep, int], str]] = None,
    text_font_size: int = 36,
    text_x_pos: int = 20,
    text_y_pos: int = 20,
    text_color: Tuple[int, ...] = WHITE,
    env_builder: EnvBuilder = builder.builder,
    print_events: Optional[bool] = False,
    player_prefixes: Optional[Sequence[str]] = None,
    default_observation: str = 'WORLD.RGB',
    reset_env_when_done: bool = False,
    initial_player_index: int = 0,
) -> None:
  """Run multiplayer environment, with per player rendering and actions.

  This function initialises a Melting Pot environment with the given
  configuration (including possible config overrides), and optionally launches
  the episode as an interactive game using pygame.  The controls are described
  in the action_map, whose keys correspond to discrete actions of the
  environment.

  Args:
    render_observation: A string consisting of the observation name to render.
        Usually 'RGB' for the third person world view.
    config_overrides: A dictionary of settings to override from the original
        `full_config.lab2d_settings`. Typically these are used to set the number
        of players.
    action_map: A dictionary of (discrete) action names to functions that detect
        the keys that correspond to its possible action values.  For example,
        for movement, we might want to have WASD navigation tied to the 'move'
        action name using `get_direction_pressed`.  See examples in the various
        play_*.py scripts.
    full_config: The full configuration for the Melting Pot environment.  These
        usually come from meltingpot/python/configs/environments.
    interactive: A RenderType representing whether the episode should be run
        with PyGame, or without any interface.  Setting interactive to false
        enables running e.g. a random agent via the action_map returning actions
        without polling PyGame (or any human input).  Non interactive runs
        ignore the screen_width, screen_height and fps parameters.
    screen_width: Width, in pixels, of the window to render the game.
    screen_height: Height, in pixels, of the window to render the game.
    fps: Frames per second of the game.
    verbose_fn: An optional function that will be executed for every step of
        the environment.  It receives the environment and the player index. This
        is typically used to print extra information that would be useful for
        debugging a running episode.
    text_display_fn: An optional function for displaying text on screen. It
        receives the environment and the player index, and returns a string to
        display on the pygame screen.
    text_font_size: the font size of onscreen text (from `text_display_fn`)
    text_x_pos: the x position of onscreen text (from `text_display_fn`)
    text_y_pos: the x position of onscreen text (from `text_display_fn`)
    text_color: RGB color of onscreen text (from `text_display_fn`)
    env_builder: The environment builder function to use. By default it is
      meltingpot.builder.
    print_events: An optional bool that if enabled will print events captured
      from the dmlab2d events API on any timestep where they occur.
    player_prefixes: If given, use these as the prefixes of player actions.
      Pressing TAB will cycle through these. If not given, use the standard
      ('1', '2', ..., numPlayers).
    default_observation: Default observation to render if 'render_observation'
      or '{player_prefix}.{render_observation}' is not found in the dict.
    reset_env_when_done: if True, reset the environment once the episode has
      terminated; useful for playing multiple episodes in a row. Note this
      will cause this function to loop infinitely.
    initial_player_index: Initial index of the player to play as. Defaults to 0.
      (Players are always switchable via the tab key.)
  """
  full_config.lab2d_settings.update(config_overrides)
  if player_prefixes is None:
    player_count = full_config.lab2d_settings.get('numPlayers', 1)
    # By default, we use lua indices (which start at 1) as player prefixes.
    player_prefixes = [f'{i+1}' for i in range(player_count)]
  else:
    player_count = len(player_prefixes)
  print(f'Running an episode with {player_count} players: {player_prefixes}.')
  env = env_builder(**full_config)

  if len(player_prefixes) != player_count:
    raise ValueError('Player prefixes, when specified, must be of the same '
                     'length as the number of players.')
  player_index = initial_player_index
  timestep = env.reset()

  score = collections.defaultdict(float)
  action_reader = ActionReader(env, action_map)

  if interactive == RenderType.PYGAME:
    pygame.init()
    pygame.display.set_caption('Melting Pot: {}'.format(
        full_config.lab2d_settings.levelName))
    font = pygame.font.SysFont(None, text_font_size)

  scale = 1
  observation_spec = env.observation_spec()
  if render_observation in observation_spec:
    obs_spec = observation_spec[render_observation]
  elif f'1.{render_observation}' in observation_spec:
    # This assumes all players have the same observation, which is true for
    # MeltingPot environments.
    obs_spec = observation_spec[f'1.{render_observation}']
  else:
    # Falls back to 'default_observation.'
    obs_spec = observation_spec[default_observation]

  observation_shape = obs_spec.shape
  observation_height = observation_shape[0]
  observation_width = observation_shape[1]
  scale = min(screen_height // observation_height,
              screen_width // observation_width)
  if interactive == RenderType.PYGAME:
    game_display = pygame.display.set_mode(
        (observation_width * scale, observation_height * scale))
    clock = pygame.time.Clock()
  stop = False

  # Game loop
  while True:

    # Check for pygame controls
    if interactive == RenderType.PYGAME:
      for event in pygame.event.get():
        if event.type == pygame.QUIT:
          stop = True

        if event.type == pygame.KEYDOWN:
          if event.key == pygame.K_TAB:
            player_index = (player_index + 1) % player_count
          break
    player_prefix = player_prefixes[player_index] if player_prefixes else ''

    if stop:
      break

    # Compute next timestep
    actions = action_reader.step(player_prefix) if player_count else []
    timestep = env.step(actions)
    if timestep.step_type == dm_env.StepType.LAST:
      if reset_env_when_done:
        timestep = env.reset()
      else:
        break

    rewards = _get_rewards(timestep)
    for i, prefix in enumerate(player_prefixes):
      if verbose_fn:
        verbose_fn(timestep, i)
      score[prefix] += rewards[prefix]
      if i == player_index and rewards[prefix] != 0:
        print(f'Player {prefix} Score: {score[prefix]}')

    # Print events if applicable
    if print_events and hasattr(env, 'events'):
      events = env.events()
      # Only print events on timesteps when there are events to print.
      if events:
        print(events)

    # pygame display
    if interactive == RenderType.PYGAME:
      # show visual observation
      if render_observation in timestep.observation:
        obs = timestep.observation[render_observation]
      elif f'{player_prefix}.{render_observation}' in timestep.observation:
        obs = timestep.observation[f'{player_prefix}.{render_observation}']
      else:
        # Fall back to default_observation.
        obs = timestep.observation[default_observation]
      obs = np.transpose(obs, (1, 0, 2))  # PyGame is column major!

      surface = pygame.surfarray.make_surface(obs)
      rect = surface.get_rect()

      surf = pygame.transform.scale(surface, (rect[2] * scale, rect[3] * scale))
      game_display.blit(surf, dest=(0, 0))

      # show text
      if text_display_fn:
        if player_count == 1:
          text_str = text_display_fn(timestep, 0)
        else:
          text_str = text_display_fn(timestep, player_index)
        img = font.render(text_str, True, text_color)
        game_display.blit(img, (text_x_pos, text_y_pos))

      # tick
      pygame.display.update()
      clock.tick(fps)

  if interactive == RenderType.PYGAME:
    pygame.quit()
  for prefix in player_prefixes:
    print('Player %s: score is %g' % (prefix, score[prefix]))
<commit_msg>Update incorrect type annotation.<commit_after># Copyright 2020 DeepMind Technologies Limited.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A simple human player for testing a Melting Pot level.

Use `WASD` keys to move the character around.
Use `Q and E` to turn the character.
Use `SPACE` to fire the zapper.
Use `TAB` to switch between players.
"""

import collections
import enum

from typing import Any, Callable, Dict, Mapping, Optional, Sequence, Tuple
import dm_env

from ml_collections import config_dict
import numpy as np
import pygame

import dmlab2d
from meltingpot.python.utils.substrates import builder

WHITE = (255, 255, 255)

MOVEMENT_MAP = {
    'NONE': 0,
    'FORWARD': 1,
    'RIGHT': 2,
    'BACKWARD': 3,
    'LEFT': 4,
}

EnvBuilder = Callable[..., dmlab2d.Environment]  # Only supporting kwargs.

ActionMap = Mapping[str, Callable[[], int]]

class RenderType(enum.Enum):
  NONE = 0
  PYGAME = 1

def get_random_direction() -> int:
  """Gets a random direction."""
  return np.random.choice(list(MOVEMENT_MAP.values()))

def get_random_turn() -> int:
  """Gets a random turn."""
  return np.random.choice([-1, 0, 1])

def get_random_fire() -> int:
  """Gets a random fire."""
  return np.random.choice([0, 1])

def get_direction_pressed() -> int:
  """Gets direction pressed."""
  key_pressed = pygame.key.get_pressed()
  if key_pressed[pygame.K_UP] or key_pressed[pygame.K_w]:
    return MOVEMENT_MAP['FORWARD']
  if key_pressed[pygame.K_RIGHT] or key_pressed[pygame.K_d]:
    return MOVEMENT_MAP['RIGHT']
  if key_pressed[pygame.K_DOWN] or key_pressed[pygame.K_s]:
    return MOVEMENT_MAP['BACKWARD']
  if key_pressed[pygame.K_LEFT] or key_pressed[pygame.K_a]:
    return MOVEMENT_MAP['LEFT']
  return MOVEMENT_MAP['NONE']

def get_turn_pressed() -> int:
  """Calculates turn increment."""
  key_pressed = pygame.key.get_pressed()
  if key_pressed[pygame.K_DELETE] or key_pressed[pygame.K_q]:
    return -1
  if key_pressed[pygame.K_PAGEDOWN] or key_pressed[pygame.K_e]:
    return 1
  return 0

def get_space_key_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_SPACE] else 0

def get_key_number_pressed() -> int:
  number_keys = [pygame.K_0, pygame.K_1, pygame.K_2, pygame.K_3, pygame.K_4,
                 pygame.K_5, pygame.K_6, pygame.K_7, pygame.K_8, pygame.K_9]
  for num in range(len(number_keys)):
    if pygame.key.get_pressed()[number_keys[num]]:
      return num
  return -1

def get_key_number_one_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_1] else 0

def get_key_number_two_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_2] else 0

def get_key_number_three_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_3] else 0

def get_key_number_four_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_4] else 0

def get_key_number_five_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_5] else 0

def get_left_control_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_LCTRL] else 0

def get_left_shift_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_LSHIFT] else 0

def get_right_shift_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_RSHIFT] else 0

def get_key_c_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_c] else 0

def get_key_z_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_z] else 0

def get_key_x_pressed() -> int:
  return 1 if pygame.key.get_pressed()[pygame.K_x] else 0

def _split_key(key: str) -> Tuple[str, str]:
  """Splits the key into player index and name."""
  return tuple(key.split('.', maxsplit=1))

def _get_rewards(timestep: dm_env.TimeStep) -> Mapping[str, float]:
  """Gets the list of rewards, one for each player."""
  rewards = {}
  for key in timestep.observation.keys():
    if key.endswith('.REWARD'):
      player_prefix, name = _split_key(key)
      if name == 'REWARD':
        rewards[player_prefix] = timestep.observation[key]
  return rewards

class ActionReader(object):
  """Convert keyboard actions to environment actions."""

  def __init__(self, env: dmlab2d.Environment, action_map: ActionMap):
    # Actions are named "<player_prefix>.<action_name>"
    self._action_map = action_map
    self._action_spec = env.action_spec()
    assert isinstance(self._action_spec, dict)
    self._action_names = set()
    for action_key in self._action_spec.keys():
      _, action_name = _split_key(action_key)
      self._action_names.add(action_name)

  def step(self, player_prefix: str) -> Mapping[str, int]:
    """Update the actions of player `player_prefix`."""
    actions = {action_key: 0 for action_key in self._action_spec.keys()}
    for action_name in self._action_names:
      actions[f'{player_prefix}.{action_name}'] = self._action_map[
          action_name]()
    return actions

def run_episode(
    render_observation: str,
    config_overrides: Dict[str, Any],
    action_map: ActionMap,
    full_config: config_dict.ConfigDict,
    interactive: RenderType = RenderType.PYGAME,
    screen_width: int = 800,
    screen_height: int = 600,
    fps: int = 8,
    verbose_fn: Optional[Callable[[dm_env.TimeStep, int], None]] = None,
    text_display_fn: Optional[Callable[[dm_env.TimeStep, int], str]] = None,
    text_font_size: int = 36,
    text_x_pos: int = 20,
    text_y_pos: int = 20,
    text_color: Tuple[int, ...] = WHITE,
    env_builder: EnvBuilder = builder.builder,
    print_events: Optional[bool] = False,
    player_prefixes: Optional[Sequence[str]] = None,
    default_observation: str = 'WORLD.RGB',
    reset_env_when_done: bool = False,
    initial_player_index: int = 0,
) -> None:
  """Run multiplayer environment, with per player rendering and actions.

  This function initialises a Melting Pot environment with the given
  configuration (including possible config overrides), and optionally launches
  the episode as an interactive game using pygame.  The controls are described
  in the action_map, whose keys correspond to discrete actions of the
  environment.

  Args:
    render_observation: A string consisting of the observation name to render.
        Usually 'RGB' for the third person world view.
    config_overrides: A dictionary of settings to override from the original
        `full_config.lab2d_settings`. Typically these are used to set the number
        of players.
    action_map: A dictionary of (discrete) action names to functions that detect
        the keys that correspond to its possible action values.  For example,
        for movement, we might want to have WASD navigation tied to the 'move'
        action name using `get_direction_pressed`.  See examples in the various
        play_*.py scripts.
    full_config: The full configuration for the Melting Pot environment.  These
        usually come from meltingpot/python/configs/environments.
    interactive: A RenderType representing whether the episode should be run
        with PyGame, or without any interface.  Setting interactive to false
        enables running e.g. a random agent via the action_map returning actions
        without polling PyGame (or any human input).  Non interactive runs
        ignore the screen_width, screen_height and fps parameters.
    screen_width: Width, in pixels, of the window to render the game.
    screen_height: Height, in pixels, of the window to render the game.
    fps: Frames per second of the game.
    verbose_fn: An optional function that will be executed for every step of
        the environment.  It receives the environment and the player index. This
        is typically used to print extra information that would be useful for
        debugging a running episode.
    text_display_fn: An optional function for displaying text on screen. It
        receives the environment and the player index, and returns a string to
        display on the pygame screen.
    text_font_size: the font size of onscreen text (from `text_display_fn`)
    text_x_pos: the x position of onscreen text (from `text_display_fn`)
    text_y_pos: the x position of onscreen text (from `text_display_fn`)
    text_color: RGB color of onscreen text (from `text_display_fn`)
    env_builder: The environment builder function to use. By default it is
      meltingpot.builder.
    print_events: An optional bool that if enabled will print events captured
      from the dmlab2d events API on any timestep where they occur.
    player_prefixes: If given, use these as the prefixes of player actions.
      Pressing TAB will cycle through these. If not given, use the standard
      ('1', '2', ..., numPlayers).
    default_observation: Default observation to render if 'render_observation'
      or '{player_prefix}.{render_observation}' is not found in the dict.
    reset_env_when_done: if True, reset the environment once the episode has
      terminated; useful for playing multiple episodes in a row. Note this
      will cause this function to loop infinitely.
    initial_player_index: Initial index of the player to play as. Defaults to 0.
      (Players are always switchable via the tab key.)
  """
  full_config.lab2d_settings.update(config_overrides)
  if player_prefixes is None:
    player_count = full_config.lab2d_settings.get('numPlayers', 1)
    # By default, we use lua indices (which start at 1) as player prefixes.
    player_prefixes = [f'{i+1}' for i in range(player_count)]
  else:
    player_count = len(player_prefixes)
  print(f'Running an episode with {player_count} players: {player_prefixes}.')
  env = env_builder(**full_config)

  if len(player_prefixes) != player_count:
    raise ValueError('Player prefixes, when specified, must be of the same '
                     'length as the number of players.')
  player_index = initial_player_index
  timestep = env.reset()

  score = collections.defaultdict(float)
  action_reader = ActionReader(env, action_map)

  if interactive == RenderType.PYGAME:
    pygame.init()
    pygame.display.set_caption('Melting Pot: {}'.format(
        full_config.lab2d_settings.levelName))
    font = pygame.font.SysFont(None, text_font_size)

  scale = 1
  observation_spec = env.observation_spec()
  if render_observation in observation_spec:
    obs_spec = observation_spec[render_observation]
  elif f'1.{render_observation}' in observation_spec:
    # This assumes all players have the same observation, which is true for
    # MeltingPot environments.
    obs_spec = observation_spec[f'1.{render_observation}']
  else:
    # Falls back to 'default_observation.'
    obs_spec = observation_spec[default_observation]

  observation_shape = obs_spec.shape
  observation_height = observation_shape[0]
  observation_width = observation_shape[1]
  scale = min(screen_height // observation_height,
              screen_width // observation_width)
  if interactive == RenderType.PYGAME:
    game_display = pygame.display.set_mode(
        (observation_width * scale, observation_height * scale))
    clock = pygame.time.Clock()
  stop = False

  # Game loop
  while True:

    # Check for pygame controls
    if interactive == RenderType.PYGAME:
      for event in pygame.event.get():
        if event.type == pygame.QUIT:
          stop = True

        if event.type == pygame.KEYDOWN:
          if event.key == pygame.K_TAB:
            player_index = (player_index + 1) % player_count
          break
    player_prefix = player_prefixes[player_index] if player_prefixes else ''

    if stop:
      break

    # Compute next timestep
    actions = action_reader.step(player_prefix) if player_count else []
    timestep = env.step(actions)
    if timestep.step_type == dm_env.StepType.LAST:
      if reset_env_when_done:
        timestep = env.reset()
      else:
        break

    rewards = _get_rewards(timestep)
    for i, prefix in enumerate(player_prefixes):
      if verbose_fn:
        verbose_fn(timestep, i)
      score[prefix] += rewards[prefix]
      if i == player_index and rewards[prefix] != 0:
        print(f'Player {prefix} Score: {score[prefix]}')

    # Print events if applicable
    if print_events and hasattr(env, 'events'):
      events = env.events()
      # Only print events on timesteps when there are events to print.
      if events:
        print(events)

    # pygame display
    if interactive == RenderType.PYGAME:
      # show visual observation
      if render_observation in timestep.observation:
        obs = timestep.observation[render_observation]
      elif f'{player_prefix}.{render_observation}' in timestep.observation:
        obs = timestep.observation[f'{player_prefix}.{render_observation}']
      else:
        # Fall back to default_observation.
        obs = timestep.observation[default_observation]
      obs = np.transpose(obs, (1, 0, 2))  # PyGame is column major!

      surface = pygame.surfarray.make_surface(obs)
      rect = surface.get_rect()

      surf = pygame.transform.scale(surface, (rect[2] * scale, rect[3] * scale))
      game_display.blit(surf, dest=(0, 0))

      # show text
      if text_display_fn:
        if player_count == 1:
          text_str = text_display_fn(timestep, 0)
        else:
          text_str = text_display_fn(timestep, player_index)
        img = font.render(text_str, True, text_color)
        game_display.blit(img, (text_x_pos, text_y_pos))

      # tick
      pygame.display.update()
      clock.tick(fps)

  if interactive == RenderType.PYGAME:
    pygame.quit()
  for prefix in player_prefixes:
    print('Player %s: score is %g' % (prefix, score[prefix]))
<|endoftext|>


<commit_before>"""
DecoratorMenus
This class allows the use of @decorators to create submenu-driven programs easily and quickly in python
the menu_item options are all passed directly to the "add_submenu" argparse function
the argument options are passed to the "add_argument" argparse function

for example use, see example.py
"""
import argparse
from ExceptionHiding import hide_last_exception

# this could be replaced in the future to accept @argument decorators and provide default arguments for the program
ap = argparse.ArgumentParser()

# this could be replaced in the future as well, with another @function to create logical command groups
sp = ap.add_subparsers()

# this will register a function or a function with arguments as a menu command
# it would be really cool to have menu_item return a menu_item class which then could be added by the above
# hypothetical future decorators
def menu_item(*args, **kwargs):
    def next_item_processor(func):
        sap = sp.add_parser(*args, **kwargs)
        sap.set_defaults(func=func)
        if hasattr(func, 'arguments'):
            for argument in func.arguments:
                #print "added {} to {}".format((argument.args, argument.kwargs), func.__name__)
                sap._add_action(argument)

        return sap
        # one could concievably return the function, in which case the function would actually be defined
        # for now, the function goes away once it is wrapped like this and the definition remains empty.
        # return f
    return next_item_processor

# this will add an argument to the function. It doesn't do anything on its own, but must follow menu_item
def argument(*args, **kwargs):
    def next_item_processor(func):
        # create an action from an ephemeral ArgumentParser
        action = argparse.ArgumentParser().add_argument(*args, **kwargs)

        #make sure that each action has a valid varname target
        if action.dest not in func.__code__.co_varnames:
            hide_last_exception()
            raise AttributeError(
                    '"{.dest}" not an argument for {.__name__}{}'\
                      .format(action, func,
                        str(func.__code__.co_varnames).replace('\'','')))

        # if the function has an "arguments" list, add to it, otherwise
        # start a new list
        func.__dict__.setdefault('arguments',[])
        func.arguments.append(action)

        # pass the func to the next higher level
        return func

    return next_item_processor

# parses the args then runs the associated function passing it the named args as parameters
def run_menu():
    args = vars(ap.parse_args())
    func = args.pop("func")
    func(**args)
<commit_msg>Clarified comments<commit_after>"""
DecoratorMenus
This class allows the use of @decorators to create submenu-driven programs easily and quickly in python
the menu_item options are all passed directly to the "add_submenu" argparse function
the argument options are passed to the "add_argument" argparse function

for example use, see example.py
"""
import argparse
from ExceptionHiding import hide_last_exception

# this could be replaced in the future to accept @argument decorators and
# provide default arguments for the program
ap = argparse.ArgumentParser()

# this could be replaced in the future as well, with another @function to create
# logical command groups
sp = ap.add_subparsers()

# this will register a function or a function with arguments as a menu command
# it returns the parser action, so theoretically you can do more with the
# actiongroup
def menu_item(*args, **kwargs):
    def next_item_processor(func):
        parser_action = sp.add_parser(*args, **kwargs)
        parser_action.set_defaults(func=func)
        if hasattr(func, 'arguments'):
            for argument in func.arguments:
                parser_action._add_action(argument)

        return parser_action
    return next_item_processor

# this will add an argument to the function. It doesn't do anything on its own,
# but must follow menu_item
def argument(*args, **kwargs):
    def next_item_processor(func):
        # create an action from an ephemeral ArgumentParser
        action = argparse.ArgumentParser().add_argument(*args, **kwargs)

        # make sure that each action has a valid varname parameter to match to
        if action.dest not in func.__code__.co_varnames:
            hide_last_exception()
            raise AttributeError(
                    '"{.dest}" not an argument for {.__name__}{}'\
                      .format(action, func,
                        str(func.__code__.co_varnames).replace('\'','')))

        # if the function has an "arguments" list, add to it, otherwise
        # start a new list
        func.__dict__.setdefault('arguments',[])
        func.arguments.append(action)

        # pass the func to the next higher level
        return func

    return next_item_processor

# parses the args then runs the associated function passing it the
# named args as parameters
def run_menu():
    args = vars(ap.parse_args())
    func = args.pop("func")
    func(**args)
<|endoftext|>


<commit_before>from geopar.triangulated_figure_class import TriangulatedFigure
from geopar.triangle_class import Triangle
from geopar.angle_class import Angle
from collections import Counter

__author__ = 'satbek'

class TFPreprocessor(object):
    """
    TFPreprocessor (Triangulated Figure Preprocessor) - encapsulates preprocessing functions.

    Each function is an implementation of a theorem. There are three theorems:
    - Theorem 1: 180-degree rule;
    - Theorem 2: 360-degree rule;
    - Theorem 3: Pairing rule.

    For more information on these theorems, please refer to the paper.
    """

    @staticmethod
    def theorem_1(a_tf):
        """
        !!! PERFECTO

        Intent: Implementation of 180-degree rule.
                Completes unknown angles of triangles in a_tf by 180-degree rule.

        PRE: isinstance(a_tf, TriangulatedFigure) is True.
        POST: For every triangle old(t) in a_tf.get_triangles(), in which exactly 2 angles are known,
              all angles of t are known.
        """

        for triangle in a_tf.get_triangles():
            if triangle.number_of_known() == 2:
                triangle.complete_unknown_angle()

    @staticmethod
    def theorem_2(a_tf):
        """
        Implementation of 360-degree rule.

        Completes unknown angles of triangles in a_tf by 360-degree rule.

        PRE1: isinstance(a_tf, TriangulatedFigure) is True.
        PRE2: EITHER
                  len( a_tf.get_interior_points() ) > 0
                  AND
                  there is exactly one unknown angle at any of a_tf.get_interior_points()
              OR
                  nothing is done

        POST: !!!
        """

        for point in a_tf.get_interior_points():
            a_tf.complete_unknown_angle_at(point)

    @staticmethod
    def theorem_3(a_tf):
        # traversing through interior points
        for point in a_tf.get_interior_points():

            # triangles around interior point
            triangles = a_tf.triangles_with_point(point)

            angle_following_list = []
            angle_preceding_list = []

            unknown_following_count = 0
            unknown_preceding_count = 0
            sum_angles = 0

            points_of_unknown_angles = []

            # traverse through triangles around interior point
            for t in triangles:
                point_following = t.point_following(point)
                point_preceding = t.point_preceding(point)

                angle_following = t.angle_of_point(point_following)
                angle_preceding = t.angle_of_point(point_preceding)

                if angle_following.is_known():
                    angle_following_list.append(angle_following)
                if angle_preceding.is_known():
                    angle_preceding_list.append(angle_preceding)

                if not angle_following.is_known():
                    unknown_following_count += 1
                    points_of_unknown_angles.append(t.get_angle_points_by_point(point_following))
                else:
                    sum_angles += angle_following

                if not angle_preceding.is_known():
                    unknown_preceding_count += 1
                    points_of_unknown_angles.append(t.get_angle_points_by_point(point_preceding))
                else:
                    sum_angles += angle_preceding

            if unknown_following_count == 1 and unknown_following_count == 1 and \
                    Counter(angle_following_list) == Counter(angle_preceding_list):
                angle_to_set = ((len(triangles) - 2) * 180 - sum_angles) / 2
                a_tf.set_angle_by_angle_points(*points_of_unknown_angles[0], angle_to_set)
                a_tf.set_angle_by_angle_points(*points_of_unknown_angles[1], angle_to_set)
<commit_msg>removed unnecessary imports<commit_after>from collections import Counter

__author__ = 'satbek'

class TFPreprocessor(object):
    """
    TFPreprocessor (Triangulated Figure Preprocessor) - encapsulates preprocessing functions.

    Each function is an implementation of a theorem. There are three theorems:
    - Theorem 1: 180-degree rule;
    - Theorem 2: 360-degree rule;
    - Theorem 3: Pairing rule.

    For more information on these theorems, please refer to the paper.
    """

    @staticmethod
    def theorem_1(a_tf):
        """
        !!! PERFECTO

        Intent: Implementation of 180-degree rule.
                Completes unknown angles of triangles in a_tf by 180-degree rule.

        PRE: isinstance(a_tf, TriangulatedFigure) is True.
        POST: For every triangle old(t) in a_tf.get_triangles(), in which exactly 2 angles are known,
              all angles of t are known.
        """

        for triangle in a_tf.get_triangles():
            if triangle.number_of_known() == 2:
                triangle.complete_unknown_angle()

    @staticmethod
    def theorem_2(a_tf):
        """
        Implementation of 360-degree rule.

        Completes unknown angles of triangles in a_tf by 360-degree rule.

        PRE1: isinstance(a_tf, TriangulatedFigure) is True.
        PRE2: EITHER
                  len( a_tf.get_interior_points() ) > 0
                  AND
                  there is exactly one unknown angle at any of a_tf.get_interior_points()
              OR
                  nothing is done

        POST: !!!
        """

        for point in a_tf.get_interior_points():
            a_tf.complete_unknown_angle_at(point)

    @staticmethod
    def theorem_3(a_tf):
        # traversing through interior points
        for point in a_tf.get_interior_points():

            # triangles around interior point
            triangles = a_tf.triangles_with_point(point)

            angle_following_list = []
            angle_preceding_list = []

            unknown_following_count = 0
            unknown_preceding_count = 0
            sum_angles = 0

            points_of_unknown_angles = []

            # traverse through triangles around interior point
            for t in triangles:
                point_following = t.point_following(point)
                point_preceding = t.point_preceding(point)

                angle_following = t.angle_of_point(point_following)
                angle_preceding = t.angle_of_point(point_preceding)

                if angle_following.is_known():
                    angle_following_list.append(angle_following)
                if angle_preceding.is_known():
                    angle_preceding_list.append(angle_preceding)

                if not angle_following.is_known():
                    unknown_following_count += 1
                    points_of_unknown_angles.append(t.get_angle_points_by_point(point_following))
                else:
                    sum_angles += angle_following

                if not angle_preceding.is_known():
                    unknown_preceding_count += 1
                    points_of_unknown_angles.append(t.get_angle_points_by_point(point_preceding))
                else:
                    sum_angles += angle_preceding

            if unknown_following_count == 1 and unknown_following_count == 1 and \
                    Counter(angle_following_list) == Counter(angle_preceding_list):
                angle_to_set = ((len(triangles) - 2) * 180 - sum_angles) / 2
                a_tf.set_angle_by_angle_points(*points_of_unknown_angles[0], angle_to_set)
                a_tf.set_angle_by_angle_points(*points_of_unknown_angles[1], angle_to_set)
<|endoftext|>


<commit_before>#coding:utf-8
'''
Created on 03.08.2012

@author: pirogov
'''
import re

from m3.ui import actions as m3_actions

#===============================================================================
# ControllerMixin
#===============================================================================
class ControllerMixin(object):
    """
    Наблюдатель за вызовом actions и кода в точках их (actions) расшрения
    """

    def __init__(self, observer, *args, **kwargs):
        super(ControllerMixin, self).__init__(*args, **kwargs)
        self._observer = observer

    def _invoke(self, request, action, stack):
        """
        Вызов action под наблюдением
        """
        stack = stack[:]
        self._observer._prepare_for_listening(action, stack)

        # обработка контроллером
        return super(ControllerMixin, self)._invoke(request, action, stack)

    def append_pack(self, pack):
        """
        Добавление ActioPack`а с регистрацией его action`ов в ObserVer`е
        """
        super(ControllerMixin, self).append_pack(pack)
        self._observer._populate_pack(pack)

#===============================================================================
# ObservableController
#===============================================================================
class ObservableController(ControllerMixin, m3_actions.ActionController):
    """
    Контроллер, поддерживающий механизм подписки через Observer
    """
    pass

#===============================================================================
# Observer
#===============================================================================
class Observer(object):
    """
    Реестр слушателей, реализующий подписку последних на действия в actions
    """
    # уровни детализации отладочного логировния
    LOG_NONE, LOG_CALLS, LOG_MORE = 0, 1, 2

    class _BeforAfterPack:
        """
        Обертка для списка слушателей, реализующая вызов before/after
        в соответствии с приоритетом, определяемым порякрм слушателей
        """

        def __init__(self, action, listeners, logger):
            self._listeners = listeners
            self._action = action
            self._logger = logger

        @staticmethod
        def lazy_chain(methods, *args):
            for m in methods:
                result = m(*args)
                if result:
                    return result

        def _execute(self, verb, *args):
            # реакция слушателей на действие @verb=(before|after)
            for listener in self._listeners:
                # слушатель должен иметь метод с именем из verb
                method = getattr(listener, verb, None)
                if method:
                    # слушатель инстанцируется каждый раз
                    listener = listener()
                    # инжекция action в слушателя
                    listener.action = self._action
                    # вызывается метод слушателя для нового экземпляра слушателя
                    result = method(listener, *args)
                    if result:
                        return result

        def pre_run(self, *args):
            return self._execute('before', *args)

        def post_run(self, *args):
            return self._execute('after', *args)

    def __init__(self, logger=lambda msg: None, verbose_level=None):
        """
        Создание наблюдателя.
        @logger - метод логирования: callable-объект,
            вызываемый для каждого сообщения (параметр - текст сообщения)
        @verbose_level - уровень подробности логирования:
            одна из констант Observer.LOG_xxx
        """
        self._logger = logger
        self._verbose_level = verbose_level

        self._registered_listeners = []
        self._action_listeners = {}
        self._actions = {}

    def _log(self, level, message):
        """
        Логирование действий с проверкой уровня подробности
        """
        if self._verbose_level >= level:
            self._logger(message)

    def _name_action(self, action):
        """
        Получение / генерация нового short_name для @action
        """
        def get_name(obj):
            name = getattr(obj, "short_name", None)
            if name and not isinstance(name, basestring):
                raise ValueError(
                    "The short_name must be a string! (%s)" % obj)
            return name

        short_name = get_name(action)
        if not short_name:
            # если у action нет short_name, генерируется новое имя по умолчанию
            cls_name = lambda obj: obj.__class__.__name__.lower()
            pack_name = get_name(action.parent) or cls_name(action.parent)
            action_name = get_name(action) or cls_name(action)
            short_name = '%s/%s' % (pack_name, action_name)
            # short_name проставляется в экземпляр action
            action.short_name = short_name

            self._log(self.LOG_MORE,
                'short_name gererated:\n\tAction\t\t %r\n\tshort_name\t "%s"'
                % (action, short_name))

        return short_name

    def _reconfigure(self):
        """
        Перестройка дерева сопоставления экшнов со слушателями
        """
        self._action_listeners = {}
        # слушатели сортируются по приоритету
        listeners = [l[1] for l in
            sorted(self._registered_listeners, key=lambda x: x[0])]
        # зарегистрированные actions получают подписчиков
        for name in self._actions:
            action_listeners = []
            for is_listen, listener in listeners:
                if is_listen(name):
                    action_listeners.append(listener)
                    self._log(self.LOG_MORE,
                        'Action linked:\n\tshort_name\t "%s"'
                        '\n\tListener\t %r' % (name, listener))
            self._action_listeners[name] = action_listeners

    def _populate_pack(self, pack):
        """
        Подписка зарегистрированных слушателей на @pack.actions
        """
        for action in pack.actions:
            short_name = self._name_action(action)
            # возбуждение исключения при коллизии short_names
            if short_name in self._actions:
                raise AssertionError(
                    'Action %r have short_name="%s", '
                    ' already registered with vs %r!'
                    % (action, short_name, self._actions[short_name]))
            self._actions[short_name] = action
        self._reconfigure()

    def subscribe(self, listener):
        """
        Декоратор, регистрирующий слушателя @listener в реестре слушателей
        """
        priority = getattr(listener, 'priority', 0) or 0

        # matcher`ы по списку рег.выр. в параметре "from" слушателя
        matchers = map(
            lambda p: re.compile(p).match,
            getattr(listener, 'listen', []))
        if matchers:
            is_listen = lambda name: any(m(name) for m in matchers)
        else:
            # если from не указан - слушатель слушает всех
            is_listen = lambda name: True

        self._registered_listeners.append(
            (priority, (is_listen, listener))
        )
        self._log(self.LOG_MORE,
            'Listener registered:\n\tListener %r' % listener)

        self._reconfigure()

        return listener

    def _configure_action(self, action, listeners):
        """
        Конфигурирует @action, инжектируя в него методы handle и handler_for,
        взаимрдействующие со слушателями @listeners
        """
        def handle(verb, arg):
            """
            Обработка данных @arg подписанными слушателями, которые имеют
            обработчик @verb
            """
            for listener in listeners:
                handler = getattr(listener, verb, None)
                if handler:
                    self._log(self.LOG_CALLS,
                        'Listener call:\n\t'
                        'Action\t %r\n\tListener %r\n\tVerb\t "%s"' %
                        (action, listener, verb))
                    # слушатель инстанцируется каждый раз
                    listener = listener()
                    # инжекция action в слушателя
                    listener.action = action
                    # вызывается метод слушателя для нового экземпляра слушателя
                    arg = handler(listener, arg)
            return arg

        def handler_for(verb):
            """
            Декоратор для обертки пользовательской функции
            обработчиком (с именем @verb) на стороне слушателей
            """
            def wrapper(fn):
                def inner(*args, **kwargs):
                    return handle(verb, fn(*args, **kwargs))
            return wrapper

        action.handle = handle
        action.handler_for = handler_for

    def _prepare_for_listening(self, action, stack):
        """
        Подготовка action к прослушиванию (инжекция методов)
        """
        short_name = getattr(action, 'short_name', self._name_action(action))
        listeners = self._action_listeners.get(short_name, [])

        # в action инжектируются методы для работы подписки,
        # причем инжектирование производится и в случае,
        # когда подписчиков нет - для консистентности
        self._configure_action(action, listeners)

        # если подписчики есть, в стек паков добавляется "пак",
        # реализующий подписку на before/after
        if listeners:
            stack.insert(0, self._BeforAfterPack(
                action, listeners,
                # инжекция логирования
                logger=lambda msg: self._log(self.LOG_CALLS, msg)
            ))
<commit_msg>Переделан механизм именования actions, как источников сигналов<commit_after>#coding:utf-8
'''
Created on 03.08.2012

@author: pirogov
'''
import re
import inspect

from m3.ui import actions as m3_actions

#===============================================================================
# ControllerMixin
#===============================================================================
class ControllerMixin(object):
    """
    Наблюдатель за вызовом actions и кода в точках их (actions) расшрения
    """

    def __init__(self, observer, *args, **kwargs):
        super(ControllerMixin, self).__init__(*args, **kwargs)
        self._observer = observer

    def _invoke(self, request, action, stack):
        """
        Вызов action под наблюдением
        """
        stack = stack[:]
        self._observer._prepare_for_listening(action, stack)

        # обработка контроллером
        return super(ControllerMixin, self)._invoke(request, action, stack)

    def append_pack(self, pack):
        """
        Добавление ActioPack`а с регистрацией его action`ов в ObserVer`е
        """
        super(ControllerMixin, self).append_pack(pack)
        self._observer._populate_pack(pack)

#===============================================================================
# ObservableController
#===============================================================================
class ObservableController(ControllerMixin, m3_actions.ActionController):
    """
    Контроллер, поддерживающий механизм подписки через Observer
    """
    pass

#===============================================================================
# Observer
#===============================================================================
class Observer(object):
    """
    Реестр слушателей, реализующий подписку последних на действия в actions
    """
    # уровни детализации отладочного логировния
    LOG_NONE, LOG_CALLS, LOG_MORE = 0, 1, 2

    class _BeforAfterPack:
        """
        Обертка для списка слушателей, реализующая вызов before/after
        в соответствии с приоритетом, определяемым порякрм слушателей
        """

        def __init__(self, action, listeners, logger):
            self._listeners = listeners
            self._action = action
            self._logger = logger

        @staticmethod
        def lazy_chain(methods, *args):
            for m in methods:
                result = m(*args)
                if result:
                    return result

        def _execute(self, verb, *args):
            # реакция слушателей на действие @verb=(before|after)
            for listener in self._listeners:
                # слушатель должен иметь метод с именем из verb
                method = getattr(listener, verb, None)
                if method:
                    # слушатель инстанцируется каждый раз
                    listener = listener()
                    # логирование
                    self._logger(
                        'Listener call:\n\t'
                        'Action\t %r\n\tListener %r\n\tVerb\t "%s"' %
                        (self._action, listener, verb))
                    # инжекция action в слушателя
                    listener.action = self._action
                    # вызывается метод слушателя для нового экземпляра слушателя
                    result = method(listener, *args)
                    if result:
                        return result

        def pre_run(self, *args):
            return self._execute('before', *args)

        def post_run(self, *args):
            return self._execute('after', *args)

    def __init__(self, logger=lambda msg: None, verbose_level=None):
        """
        Создание наблюдателя.
        @logger - метод логирования: callable-объект,
            вызываемый для каждого сообщения (параметр - текст сообщения)
        @verbose_level - уровень подробности логирования:
            одна из констант Observer.LOG_xxx
        """
        self._logger = logger
        self._verbose_level = verbose_level

        self._registered_listeners = []
        self._action_listeners = {}
        self._actions = {}

    def _log(self, level, message):
        """
        Логирование действий с проверкой уровня подробности
        """
        if self._verbose_level >= level:
            self._logger(message)

    def _name_action(self, action):
        """
        Получение / генерация полного имени для @action
        """
        name = getattr(action, '_observing_name', None)
        if not name:
            pack_cls = action.parent.__class__
            # имя подписки будет иметь вид "пакет/КлассПака/КлассAction"
            name = '%s/%s/%s' % (
                inspect.getmodule(pack_cls).__package__,
                pack_cls.__name__,
                action.__class__.__name__,
            )
            # название подписки проставляется в экземпляр action
            action._observing_name = name

            self._log(self.LOG_MORE,
                'Name gererated:\n\tAction\t %r\n\tname\t "%s"'
                % (action, name))

        return name

    def _reconfigure(self):
        """
        Перестройка дерева сопоставления экшнов со слушателями
        """
        self._action_listeners = {}
        # слушатели сортируются по приоритету
        listeners = [l[1] for l in
            sorted(self._registered_listeners, key=lambda x: x[0])]
        # зарегистрированные actions получают подписчиков
        for name in self._actions:
            action_listeners = []
            for is_listen, listener in listeners:
                if is_listen(name):
                    action_listeners.append(listener)
                    self._log(self.LOG_MORE,
                        'Action linked:\n\tshort_name\t "%s"'
                        '\n\tListener\t %r' % (name, listener))
            self._action_listeners[name] = action_listeners

    def _populate_pack(self, pack):
        """
        Подписка зарегистрированных слушателей на @pack.actions
        """
        # регистрация пак, как основного для модели

        for action in pack.actions:
            name = self._name_action(action)
            # возбуждение исключения при коллизии short_names
            if name in self._actions:
                raise AssertionError(
                    'Name="%s" can not be registered for action %r,\n'
                    'because this name ristered for %r!'
                    % (action, name, self._actions[name]))
            self._actions[name] = action
        self._reconfigure()

    def subscribe(self, listener):
        """
        Декоратор, регистрирующий слушателя @listener в реестре слушателей
        """
        priority = getattr(listener, 'priority', 0) or 0

        # matcher`ы по списку рег.выр. в параметре "from" слушателя
        matchers = map(
            lambda p: re.compile(p).match,
            getattr(listener, 'listen', []))
        if matchers:
            is_listen = lambda name: any(m(name) for m in matchers)
        else:
            # если from не указан - слушатель слушает всех
            is_listen = lambda name: True

        self._registered_listeners.append(
            (priority, (is_listen, listener))
        )
        self._log(self.LOG_MORE,
            'Listener registered:\n\tListener %r' % listener)

        self._reconfigure()

        return listener

    def _configure_action(self, action, listeners):
        """
        Конфигурирует @action, инжектируя в него методы handle и handler_for,
        взаимрдействующие со слушателями @listeners
        """
        def handle(verb, arg):
            """
            Обработка данных @arg подписанными слушателями, которые имеют
            обработчик @verb
            """
            for listener in listeners:
                handler = getattr(listener, verb, None)
                if handler:
                    self._log(self.LOG_CALLS,
                        'Listener call:\n\t'
                        'Action\t %r\n\tListener %r\n\tVerb\t "%s"' %
                        (action, listener, verb))
                    # слушатель инстанцируется каждый раз
                    listener = listener()
                    # инжекция action в слушателя
                    listener.action = action
                    # вызывается метод слушателя для нового экземпляра слушателя
                    arg = handler(listener, arg)
            return arg

        def handler_for(verb):
            """
            Декоратор для обертки пользовательской функции
            обработчиком (с именем @verb) на стороне слушателей
            """
            def wrapper(fn):
                def inner(*args, **kwargs):
                    return handle(verb, fn(*args, **kwargs))
            return wrapper

        action.handle = handle
        action.handler_for = handler_for

    def _prepare_for_listening(self, action, stack):
        """
        Подготовка action к прослушиванию (инжекция методов)
        """
        listeners = self._action_listeners.get(self._name_action(action), [])

        # в action инжектируются методы для работы подписки,
        # причем инжектирование производится и в случае,
        # когда подписчиков нет - для консистентности
        self._configure_action(action, listeners)

        # если подписчики есть, в стек паков добавляется "пак",
        # реализующий подписку на before/after
        if listeners:
            stack.insert(0, self._BeforAfterPack(
                action, listeners,
                # инжекция логирования
                logger=lambda msg: self._log(self.LOG_CALLS, msg)
            ))
<|endoftext|>


<commit_before>#!/usr/bin/env python
"""
pyroTorrent module.
"""

from flup.server.fcgi import WSGIServer
from jinja2 import Environment, PackageLoader

from webtool import WebTool, read_post_data

import datetime
import time

# For unescaping
import urllib

# Regex
import re

from beaker.middleware import SessionMiddleware

import simplejson as json

from config import *

from sessionhack import SessionHack, SessionHackException

from model.rtorrent import RTorrent
from model.torrent import Torrent

from lib.torrentrequester import TorrentRequester

def pyroTorrentApp(env, start_response):
    """
    pyroTorrent main function.
    """

    # Log here if you want

    r = wt.apply_rule(env['REQUEST_URI'], env)

    # 404
    if r is None:
        start_response('404 Not Found', [('Content-Type', 'text/html')])
        tmpl = jinjaenv.get_template('404.html')

        return template_render(tmpl, {
            'url' : env['REQUEST_URI'], 'session' : env['beaker.session']},
            default_page=False)

    elif type(r) in (tuple, list) and len(r) >= 1:
        # Respond with custom type.
        start_response('200 OK', [('Content-Type', r[0])])
        r = r[1]

    # 200
    else:
        start_response('200 OK', [('Content-Type', 'text/html;charset=utf8')])

    # Response data
    return [r]

def template_render(template, vars, default_page=True):
    """
        Template Render is a helper that initialisaes basic template variables
        and handles unicode encoding.
    """
    vars['base_url'] = BASE_URL
    vars['static_url'] = STATIC_URL

    # You can add more to vars here, such as a cached libTorrent version.
    if default_page:
        global libtorrentversion
        vars['libtorrentversion'] = libtorrentversion

    return unicode(template.render(vars)).encode('utf8')

# These *_page functions are what you would call ``controllers''.
def main_page(env):

    t = TorrentRequester('')

    t.get_name().get_download_rate().get_upload_rate() \
            .is_complete().get_size_bytes().get_download_total().get_hash()

    torrents = t.all()

    global global_rtorrent
    r = global_rtorrent.query().get_upload_rate().get_download_rate()

    rtorrent_data = r.first()

    tmpl = jinjaenv.get_template('download_list.html')

    return template_render(tmpl, {'session' : env['beaker.session'],
        'torrents' : torrents, 'rtorrent_data' : rtorrent_data} )

def torrent_info_page(env, torrent_hash):
    t = Torrent(torrenthash, **rtorrent_config)

    q = t.query()

    q.get_name().get_size_bytes().get_bytes_left().get_loaded_file()

    torrent = q.all()[0] # .first() ?

    # FIXME THIS IS UGLY
    host = rtorrent_config['host']
    port = rtorrent_config['port']
    url = rtorrent_config['url']

    files = TorrentFileRequester(host, port, url, t._hash, '').get_path_components().all()

    global global_rtorrent
    r = global_rtorrent.query().get_upload_rate().get_download_rate()

    rtorrent_data = r.first()

    tmpl = jinjaenv.get_template('torrentinfo.html')

    return template_render(tmpl, {'session' : env['beaker.session'],
        'torrent' : torrent, 'files' : files, 'rtorrent_data' : rtorrent_data} )

if __name__ == '__main__':
    jinjaenv = Environment(loader=PackageLoader('pyrotorrent', 'templates'))
    jinjaenv.autoescape = True
    wt = WebTool()

    # Add all rules
    execfile('rules.py')

    # Global helpers
    global_rtorrent = RTorrent(**rtorrent_config)

    libtorrentversion = global_rtorrent.get_libtorrent_version()

    WSGIServer(SessionMiddleware(pyroTorrentApp), \
            session_options).run()
   # WSGIServer(SessionMiddleware(SessionHack(pyroTorrentApp), \
   #         session_options)).run()
<commit_msg>pyroTorrent: torrenthash -> torrent_hash.<commit_after>#!/usr/bin/env python
"""
pyroTorrent module.
"""

from flup.server.fcgi import WSGIServer
from jinja2 import Environment, PackageLoader

from webtool import WebTool, read_post_data

import datetime
import time

# For unescaping
import urllib

# Regex
import re

from beaker.middleware import SessionMiddleware

import simplejson as json

from config import *

from sessionhack import SessionHack, SessionHackException

from model.rtorrent import RTorrent
from model.torrent import Torrent

from lib.torrentrequester import TorrentRequester

def pyroTorrentApp(env, start_response):
    """
    pyroTorrent main function.
    """

    # Log here if you want

    r = wt.apply_rule(env['REQUEST_URI'], env)

    # 404
    if r is None:
        start_response('404 Not Found', [('Content-Type', 'text/html')])
        tmpl = jinjaenv.get_template('404.html')

        return template_render(tmpl, {
            'url' : env['REQUEST_URI'], 'session' : env['beaker.session']},
            default_page=False)

    elif type(r) in (tuple, list) and len(r) >= 1:
        # Respond with custom type.
        start_response('200 OK', [('Content-Type', r[0])])
        r = r[1]

    # 200
    else:
        start_response('200 OK', [('Content-Type', 'text/html;charset=utf8')])

    # Response data
    return [r]

def template_render(template, vars, default_page=True):
    """
        Template Render is a helper that initialisaes basic template variables
        and handles unicode encoding.
    """
    vars['base_url'] = BASE_URL
    vars['static_url'] = STATIC_URL

    # You can add more to vars here, such as a cached libTorrent version.
    if default_page:
        global libtorrentversion
        vars['libtorrentversion'] = libtorrentversion

    return unicode(template.render(vars)).encode('utf8')

# These *_page functions are what you would call ``controllers''.
def main_page(env):

    t = TorrentRequester('')

    t.get_name().get_download_rate().get_upload_rate() \
            .is_complete().get_size_bytes().get_download_total().get_hash()

    torrents = t.all()

    global global_rtorrent
    r = global_rtorrent.query().get_upload_rate().get_download_rate()

    rtorrent_data = r.first()

    tmpl = jinjaenv.get_template('download_list.html')

    return template_render(tmpl, {'session' : env['beaker.session'],
        'torrents' : torrents, 'rtorrent_data' : rtorrent_data} )

def torrent_info_page(env, torrent_hash):
    t = Torrent(torrent_hash, **rtorrent_config)

    q = t.query()

    q.get_name().get_size_bytes().get_bytes_left().get_loaded_file()

    torrent = q.all()[0] # .first() ?

    # FIXME THIS IS UGLY
    host = rtorrent_config['host']
    port = rtorrent_config['port']
    url = rtorrent_config['url']

    files = TorrentFileRequester(host, port, url, t._hash, '').get_path_components().all()

    global global_rtorrent
    r = global_rtorrent.query().get_upload_rate().get_download_rate()

    rtorrent_data = r.first()

    tmpl = jinjaenv.get_template('torrentinfo.html')

    return template_render(tmpl, {'session' : env['beaker.session'],
        'torrent' : torrent, 'files' : files, 'rtorrent_data' : rtorrent_data} )

if __name__ == '__main__':
    jinjaenv = Environment(loader=PackageLoader('pyrotorrent', 'templates'))
    jinjaenv.autoescape = True
    wt = WebTool()

    # Add all rules
    execfile('rules.py')

    # Global helpers
    global_rtorrent = RTorrent(**rtorrent_config)

    libtorrentversion = global_rtorrent.get_libtorrent_version()

    WSGIServer(SessionMiddleware(pyroTorrentApp), \
            session_options).run()
   # WSGIServer(SessionMiddleware(SessionHack(pyroTorrentApp), \
   #         session_options)).run()
<|endoftext|>


<commit_before>##############################################################################
# PyCharm Community Edition
# -*- coding: utf-8 -*-
"""
Orthologs-Project
Directory_management updated on 11/15/2016 at 11:30 AM
##############################################################################

    Input:  A string that represents a path for the main file systems

    Output:  Custom class variables for import in the project file template.

    Description:  This file manges the directories for projects created in PyCharm.
    Each new project gets it's own function.

##############################################################################
@author: rgilmore
"""
##############################################################################
# Libraries:

import json
import os
import shutil
import time
from pathlib import Path

from Orthologs.manager.dir_map import treelib2

from Manager.dir_map.json_to_newick import _parse_json

#from project_mana import project_mana  # //TODO-ROB: Add a configure function to proj_mana to get the root directory using the project name
##############################################################################
# Directory Initializations:

class dir_mana(object):
    """This class organizes a directory tree for a project.
    The dir_mana() class will help with organization and it will
    help to instantly access the proper directories on command.

    It is advised to set up a function that is named after the project.
    Each new project will be called from the project() function.

    See GPCR-Orthologs-Project."""

# //TODO-ROB Add JSON loading of the different directory variables
# //TODO-Rob change project to projects and add another variable called project_type

    def __init__(self, home=os.getcwd(), proj_mana="proj_mana.json"):
        """Initialize the directory tree for the project.
        Each project will have a home directory in addition to the following:
        ."""
        # config = tablib.Dataset().load(open('config.yaml').read())
        self.__file_home = Path(home)  # Home of the file calling this class

        self.current_user = os.environ['ACTIVE_USER']  # TODO-ROB:  FLASK detail
        self.user_dict = {}
        self.user_path = self.users / Path(self.current_user)
        self.user_dict['username'] = self.user_path
        self.user_dict['index'] = self.user_path / Path('index')
        self.user_dict['log'] = self.user_path / Path('log')
        self.user_dict['manuscripts'] = self.user_path / Path('manuscripts')
        self.user_dict['other'] = self.user_path / Path('other')
        self.user_dict['projects'] = self.user_path / Path('projects')

        self.project_status = ''
        self.user_project_dict = {}
        self.user_project_dict['public'] = self.user_dict['projects'] / Path('public')
        self.user_project_dict['private'] = self.user_dict['projects'] / Path('private')
        # TODO-ROB: Make a project category that allows users to make some research targets private and othes public
        self.user_project_dict['other'] = self.user_dict['projects'] / Path('other')
        self.current_dataset = ''
        self.user_project_dict[self.current_dataset] = self.user_project_dict[self.project_status] / Path(self.current_dataset)
        self.project_research_target_dict = {}
        if self.project_status is 'other':
            self.user_project_dict[self.current_dataset]

        self.dataset_dict = {}
        self.dataset_dict['data_set'] = self.user_project_dict[self.project_status] / Path(self.current_dataset)
        self.dataset_dict[]

        self.re
        #self.__venv_home = os.environ['VIRTUAL_ENV']
        #self.__python = self.__venv_home / Path('bin')

        #self.__project_management = self.proj_mana(proj_mana)  # dict {[projects] : {[data-sets] : [research targets]}}
        # if pdr is False:
        #     self.__project = project  # Project Name
        #     self.__project_home = Path()  # Initialize home to a PathLike object.
        #     self.__dataset = self.__project_home / Path(dataset)
        #
        #     # The project name is also the name of the project's main directory.
        #     # Make sure that the file's home is in the project's directory tree.
        #     if self.__project not in list(self.__file_home.parts):
        #         raise NotADirectoryError
        #     # If the file isn't in the directory tree throw an Error.
        #
        #     # Append the project_home variable with child directories until the
        #     # project's main directory is reached.
        #     for item in list(self.__file_home.parts):
        #         self.__project_home = self.__project_home / Path(item)
        #         if item == self.__project:
        #             break
        #     self.project()
        # else:
        #
        #     self.top_level_struct()
        #     self.project()
        #
        # if exists is False:
        #     self.create_dirs()

    # Map the main project directory.
    def get_dir_map(self, top, ignore=None):
        default_ignore = ['.git', '.idea']
        if ignore is not None:
            ignore += default_ignore
        else:
            ignore = default_ignore
        # Treelib will help to map everything and create a json at the same time
        tree = treelib2.Tree()
        tree.create_node('.', top)
        # Walk through the directory of choice (top)
        # Topdown is true so that directories can be modified in place
        for root, dirs, files in os.walk(top, topdown=True):
            # Only remove directories from the top
            if root == top:
                print(root)
                try:
                    dirs[:] = set(dirs) - set(ignore)  # Remove directories from os.walk()
                    print(dirs)
                except ValueError:
                    pass
            for d in dirs:
                rd = str(Path(root) / Path(d))
                tree.create_node(d, identifier=rd, parent=root)
            for f in files:
                tree.create_node(f, parent=root)
        return tree

    def user_dir_config(self, username):
        # TODO-ROB USE SQL here to see if the user db contains the username
        if username in os.listdir(self.users):
            return UserWarning('This user already exists')
        else:
            # TODO-ROB CREATE THESE IN A VIRTUAL ENVIRONMENT FOR EACH USER
            # TODO-ROB The virtual environment can be the name of the user
            # TODO-ROB When the user logs in, they will activate the virtual environment
            for path in self.user_dict.values():
                Path.mkdir(path, parents=True, exist_ok=True)
            for path in self.user_project_dict.values():
                Path.mkdir(path, parents=True, exist_ok=True)

    def user_project_config(self, project_name, private=False):
        if private is False:
            path = self.user_project_dict['public']
        else:
            path = self.user_project_dict['private']
        Path.mkdir(path / Path(project_name))

    def get_newick_dir_map(self, top, ignore=None):
        """Takes a treelib tree created by get_dir_map and returns
        a tree a dir_map in newick format.  This will be useful for Bio.Phylo
        applications."""

        tree = self.get_dir_map(top, ignore)
        Ntree = _parse_json(tree.to_jsonnewick())
        return Ntree

    def proj_mana(self, js_file):
        with open(js_file, 'r') as file:
            pdr = json.load(file)
        return pdr

    def top_level_struct(self):
        # Create variables for the default directories.
        self.docs = self.__project_home / Path('docs')
        self.lib = self.__project_home / Path('lib')
        self.misc = self.__project_home / Path('misc')
        self.users = self.__project_home / Path('users')
        self.__user_home = self.users / Path(os.environ['USER'])  # TODO-ROB:  Set 'USER' environment variable w/ flask
        # TODO-ROB:  configure during registration for all usernames to be lowercase
        self.web = self.__project_home / Path('web')

    def project(self, p_rt):
        """The projects function helps keep up with different projects.
        Create a different function for every new project."""
        # p_rt is the project research target dictionary
        # the key is the project name and the value is a list of target areas for research
        # Example:  {"GPCR_Project" : ["comparative_evolution", "comparative_polymorphism", "natural_selection"]}
        self.orthologs()

        for project in projects:
            Path.mkdir(self.__project_home / Path(project))

    def orthologs(self):
        """Orthologs-Project"""

        # lib directory
        self.archives = self.lib / Path('archives')
        self.bash = self.lib / Path('bash')
        self.index = self.lib / Path('index')
        self.log = self.lib / Path('log')
        self.perl = self.lib / Path('perl')
        self.python = self.lib / Path('python')
        self.py_classes = self.python / Path('classes')
        self.r = self.lib / Path('r')
        self.scripts = self.lib / Path('scripts')
        self.multiprocessing = self.scripts / Path('multiprocessing')
        self.templates = self.lib / Path('templates')
        # raw_data directory
        self.gi_lists = self.raw_data / Path('gi_lists')

    def gpcr(self):
        """GPCR-Orthologs-Project"""
        self.APP = self.__project_home
        self.WWW = self.APP / Path('www')
        self.APP_DATA = self.APP / Path('Data')
        self.APP_TESTS = self.APP / Path('App-Tests')
        self.ROB_TEST = self.APP_TESTS / Path('Rob')
        self.SHAE_TEST = self.APP_TESTS / Path('Shaurita')
        self.OTHER_TEST = self.APP_TESTS / Path('Other')

    def create_dirs(self):
        for key, value in self.__dict__.items():
            if str(type(value)).__contains__('pathlib') is True:
                Path.mkdir(value, parents=True, exist_ok=True)

    # # //TODO-ROB Find a different way to return a
    # def path_list_make(self, path, o_path=None):
    #     # Takes a path and reduces it to a list of directories within the project
    #     # An optional attribute (o_path) is give so that a deeper path within the project can be used
    #     home = str(self.__project_home).split('/')
    #     path_list = str(path).split('/')
    #     for item in home:
    #         if item in path_list:
    #             path_list.remove(item)
    #     # path_list = set(p) - set(home)
    #     if o_path is not None:
    #         o_path = str(o_path).split('/')
    #         for item in o_path:
    #             if item in path_list:
    #                 path_list.remove(item)
    #         # path_list = set(path_list) - set(o_path)
    #     return path_list

    # //TODO-ROB utilize Path.mkdir(parents=TRUE) instead
    # def dir_make(self, path, path_list):
    #     # Takes a path list which is a list of folder names
    #     # path_list created by str(path).split('/')
    #     # The path_list appends to path, which is already an established directory inside the project
    #     t = None
    #     for item in path_list:
    #
    #         if os.path.isdir(path + '/' + item): # If for some reason the directory already exists...
    #             path += '/' + item  # Append a directory
    #             continue
    #         path += '/' + item  # Append a directory
    #         os.mkdir(path)
    #     if len(os.listdir(path)) > 0:
    #         path, t = self.dir_archive(path, path_list='')
    #     return path, t
    #
    # # //TODO-ROB Change to using a compression module https://pymotw.com/2/compression.html
    # def dir_archive(self, path, path_list):
    #     # Use the path that you want to update/add to
    #     # Returns path and the time stamp (could be None)
    #     unique_dir = False
    #     archive_path = path
    #     for item in path_list:
    #
    #         path += '/' + item  # Append a directory
    #         if os.path.isdir(path):  # If the child directory exists
    #             archive_path = path  # Then update the dir_archive path and continue
    #             continue
    #         else:                     # If the child directory doesnt exist
    #             unique_dir = True     # Then raise the flag
    #             os.mkdir(path)        # And make a directory
    #
    #     if unique_dir is False:  # Only dir_archive if the final child directory is not unique (via unique_dir = False)
    #         t = time.strftime("%m%d%Y-%I%M%S")
    #         new_archive = self.Archive + '/' + t  # Creates a time stamped directory
    #
    #         os.mkdir(new_archive)
    #         for item in os.listdir(archive_path):
    #             if os.path.isfile(archive_path + '/' + item):  # Only dir_archive the FILES
    #                 shutil.move(archive_path + '/' + item, new_archive)
    #         return path, t
    #     else:
    #         return path, None

# TODO-ROB Add a instance that stores new paths inside of a text file and has an updating/overwriting ability

# TODO-ROB CALL ON THIS INSTNACE IN __INIT__ SO THAT VARIABLES CAN BE CREATED BASED ON THE PATHS

<commit_msg>Reworking Directory Management.  Taking advantage of our PyPi package which contains cookiecutter templates.  Creating a simple class hierarchy (PyPi Package, Repository, User, Project (dataset), Research Target, and Application directory management).<commit_after>##############################################################################
# PyCharm Community Edition
# -*- coding: utf-8 -*-
"""
Orthologs-Project
Directory_management updated on 11/15/2016 at 11:30 AM
##############################################################################

    Input:  A string that represents a path for the main file systems

    Output:  Custom class variables for import in the project file template.

    Description:  This file manges the directories for projects created in PyCharm.
    Each new project gets it's own function.

##############################################################################
@author: rgilmore
"""
##############################################################################
# Libraries:

import json
import os
import shutil
import time
from pathlib import Path

from Orthologs.manager.dir_map import treelib2

from Manager.dir_map.json_to_newick import _parse_json

#from project_mana import project_mana  # //TODO-ROB: Add a configure function to proj_mana to get the root directory using the project name
##############################################################################
# Directory Initializations:

class dir_mana(object):
    """This class organizes a directory tree for a project.
    The Mana() class will help with organization and it will
    help to instantly access the proper directories on command.

    It is advised to set up a function that is named after the project.
    Each new project will be called from the project() function.

    See GPCR-Orthologs-Project."""

# //TODO-ROB Add JSON loading of the different directory variables
# //TODO-Rob change project to projects and add another variable called project_type

    def __init__(self, home=os.getcwd(), proj_mana="proj_mana.json"):
        """Initialize the directory tree for the project.
        Each project will have a home directory in addition to the following:
        ."""
        # config = tablib.Dataset().load(open('config.yaml').read())
        self.__file_home = Path(home)  # Home of the file calling this class

        self.current_user = os.environ['ACTIVE_USER']  # TODO-ROB:  FLASK detail
        self.user_dict = {}
        self.user_path = self.users / Path(self.current_user)
        self.user_dict['username'] = self.user_path
        self.user_dict['index'] = self.user_path / Path('index')
        self.user_dict['log'] = self.user_path / Path('log')
        self.user_dict['manuscripts'] = self.user_path / Path('manuscripts')
        self.user_dict['other'] = self.user_path / Path('other')
        self.user_dict['projects'] = self.user_path / Path('projects')

        self.project_status = ''
        self.user_project_dict = {}
        self.user_project_dict['public'] = self.user_dict['projects'] / Path('public')
        self.user_project_dict['private'] = self.user_dict['projects'] / Path('private')
        # TODO-ROB: Make a project category that allows users to make some research targets private and othes public
        self.user_project_dict['other'] = self.user_dict['projects'] / Path('other')
        self.current_dataset = ''
        self.user_project_dict[self.current_dataset] = self.user_project_dict[self.project_status] / Path(self.current_dataset)
        self.project_research_target_dict = {}
        if self.project_status is 'other':
            self.user_project_dict[self.current_dataset]

        self.dataset_dict = {}
        self.dataset_dict['data_set'] = self.user_project_dict[self.project_status] / Path(self.current_dataset)
        self.dataset_dict[]

        self.re
        #self.__venv_home = os.environ['VIRTUAL_ENV']
        #self.__python = self.__venv_home / Path('bin')

        #self.__project_management = self.proj_mana(proj_mana)  # dict {[projects] : {[data-sets] : [research targets]}}
        # if pdr is False:
        #     self.__project = project  # Project Name
        #     self.__project_home = Path()  # Initialize home to a PathLike object.
        #     self.__dataset = self.__project_home / Path(dataset)
        #
        #     # The project name is also the name of the project's main directory.
        #     # Make sure that the file's home is in the project's directory tree.
        #     if self.__project not in list(self.__file_home.parts):
        #         raise NotADirectoryError
        #     # If the file isn't in the directory tree throw an Error.
        #
        #     # Append the project_home variable with child directories until the
        #     # project's main directory is reached.
        #     for item in list(self.__file_home.parts):
        #         self.__project_home = self.__project_home / Path(item)
        #         if item == self.__project:
        #             break
        #     self.project()
        # else:
        #
        #     self.top_level_struct()
        #     self.project()
        #
        # if exists is False:
        #     self.create_dirs()

    # Map the main project directory.
    def get_dir_map(self, top, ignore=None):
        default_ignore = ['.git', '.idea']
        if ignore is not None:
            ignore += default_ignore
        else:
            ignore = default_ignore
        # Treelib will help to map everything and create a json at the same time
        tree = treelib2.Tree()
        tree.create_node('.', top)
        # Walk through the directory of choice (top)
        # Topdown is true so that directories can be modified in place
        for root, dirs, files in os.walk(top, topdown=True):
            # Only remove directories from the top
            if root == top:
                print(root)
                try:
                    dirs[:] = set(dirs) - set(ignore)  # Remove directories from os.walk()
                    print(dirs)
                except ValueError:
                    pass
            for d in dirs:
                rd = str(Path(root) / Path(d))
                tree.create_node(d, identifier=rd, parent=root)
            for f in files:
                tree.create_node(f, parent=root)
        return tree

    def user_dir_config(self, username):
        # TODO-ROB USE SQL here to see if the user db contains the username
        if username in os.listdir(self.users):
            return UserWarning('This user already exists')
        else:
            # TODO-ROB CREATE THESE IN A VIRTUAL ENVIRONMENT FOR EACH USER
            # TODO-ROB The virtual environment can be the name of the user
            # TODO-ROB When the user logs in, they will activate the virtual environment
            for path in self.user_dict.values():
                Path.mkdir(path, parents=True, exist_ok=True)
            for path in self.user_project_dict.values():
                Path.mkdir(path, parents=True, exist_ok=True)

    def user_project_config(self, project_name, private=False):
        if private is False:
            path = self.user_project_dict['public']
        else:
            path = self.user_project_dict['private']
        Path.mkdir(path / Path(project_name))

    def get_newick_dir_map(self, top, ignore=None):
        """Takes a treelib tree created by get_dir_map and returns
        a tree a dir_map in newick format.  This will be useful for Bio.Phylo
        applications."""

        tree = self.get_dir_map(top, ignore)
        Ntree = _parse_json(tree.to_jsonnewick())
        return Ntree

    def proj_mana(self, js_file):
        with open(js_file, 'r') as file:
            pdr = json.load(file)
        return pdr

    def top_level_struct(self):
        # Create variables for the default directories.
        self.docs = self.__project_home / Path('docs')
        self.lib = self.__project_home / Path('lib')
        self.misc = self.__project_home / Path('misc')
        self.users = self.__project_home / Path('users')
        self.__user_home = self.users / Path(os.environ['USER'])  # TODO-ROB:  Set 'USER' environment variable w/ flask
        # TODO-ROB:  configure during registration for all usernames to be lowercase
        self.web = self.__project_home / Path('web')

    def project(self, p_rt):
        """The projects function helps keep up with different projects.
        Create a different function for every new project."""
        # p_rt is the project research target dictionary
        # the key is the project name and the value is a list of target areas for research
        # Example:  {"GPCR_Project" : ["comparative_evolution", "comparative_polymorphism", "natural_selection"]}
        self.orthologs()

        for project in projects:
            Path.mkdir(self.__project_home / Path(project))

    def orthologs(self):
        """Orthologs-Project"""

        # lib directory
        self.archives = self.lib / Path('archives')
        self.bash = self.lib / Path('bash')
        self.index = self.lib / Path('index')
        self.log = self.lib / Path('log')
        self.perl = self.lib / Path('perl')
        self.python = self.lib / Path('python')
        self.py_classes = self.python / Path('classes')
        self.r = self.lib / Path('r')
        self.scripts = self.lib / Path('scripts')
        self.multiprocessing = self.scripts / Path('multiprocessing')
        self.templates = self.lib / Path('templates')
        # raw_data directory
        self.gi_lists = self.raw_data / Path('gi_lists')

    def gpcr(self):
        """GPCR-Orthologs-Project"""
        self.APP = self.__project_home
        self.WWW = self.APP / Path('www')
        self.APP_DATA = self.APP / Path('Data')
        self.APP_TESTS = self.APP / Path('App-Tests')
        self.ROB_TEST = self.APP_TESTS / Path('Rob')
        self.SHAE_TEST = self.APP_TESTS / Path('Shaurita')
        self.OTHER_TEST = self.APP_TESTS / Path('Other')

    def create_dirs(self):
        for key, value in self.__dict__.items():
            if str(type(value)).__contains__('pathlib') is True:
                Path.mkdir(value, parents=True, exist_ok=True)

    # # //TODO-ROB Find a different way to return a
    # def path_list_make(self, path, o_path=None):
    #     # Takes a path and reduces it to a list of directories within the project
    #     # An optional attribute (o_path) is give so that a deeper path within the project can be used
    #     home = str(self.__project_home).split('/')
    #     path_list = str(path).split('/')
    #     for item in home:
    #         if item in path_list:
    #             path_list.remove(item)
    #     # path_list = set(p) - set(home)
    #     if o_path is not None:
    #         o_path = str(o_path).split('/')
    #         for item in o_path:
    #             if item in path_list:
    #                 path_list.remove(item)
    #         # path_list = set(path_list) - set(o_path)
    #     return path_list

    # //TODO-ROB utilize Path.mkdir(parents=TRUE) instead
    # def dir_make(self, path, path_list):
    #     # Takes a path list which is a list of folder names
    #     # path_list created by str(path).split('/')
    #     # The path_list appends to path, which is already an established directory inside the project
    #     t = None
    #     for item in path_list:
    #
    #         if os.path.isdir(path + '/' + item): # If for some reason the directory already exists...
    #             path += '/' + item  # Append a directory
    #             continue
    #         path += '/' + item  # Append a directory
    #         os.mkdir(path)
    #     if len(os.listdir(path)) > 0:
    #         path, t = self.dir_archive(path, path_list='')
    #     return path, t
    #
    # # //TODO-ROB Change to using a compression module https://pymotw.com/2/compression.html
    # def dir_archive(self, path, path_list):
    #     # Use the path that you want to update/add to
    #     # Returns path and the time stamp (could be None)
    #     unique_dir = False
    #     archive_path = path
    #     for item in path_list:
    #
    #         path += '/' + item  # Append a directory
    #         if os.path.isdir(path):  # If the child directory exists
    #             archive_path = path  # Then update the dir_archive path and continue
    #             continue
    #         else:                     # If the child directory doesnt exist
    #             unique_dir = True     # Then raise the flag
    #             os.mkdir(path)        # And make a directory
    #
    #     if unique_dir is False:  # Only dir_archive if the final child directory is not unique (via unique_dir = False)
    #         t = time.strftime("%m%d%Y-%I%M%S")
    #         new_archive = self.Archive + '/' + t  # Creates a time stamped directory
    #
    #         os.mkdir(new_archive)
    #         for item in os.listdir(archive_path):
    #             if os.path.isfile(archive_path + '/' + item):  # Only dir_archive the FILES
    #                 shutil.move(archive_path + '/' + item, new_archive)
    #         return path, t
    #     else:
    #         return path, None

# TODO-ROB Add a instance that stores new paths inside of a text file and has an updating/overwriting ability

# TODO-ROB CALL ON THIS INSTNACE IN __INIT__ SO THAT VARIABLES CAN BE CREATED BASED ON THE PATHS

<|endoftext|>