<commit_before>"""Different classes to parse dicts/lists returned from json into meaningful data objects."""

from .enums import *
from .dictmodel import AttributeModel, Attribute, JsonList, Nullable, PreProcessInt, DateConverter

class Score(AttributeModel):
    """Abstract class representing a score.

    Attributes
    -----------
    score : int
        The score value
    maxcombo : int
        Largest combo achieved
    count50 : int
        Number of "50" hits.
        In catch: number of "droplet" hits
    count100 : int
        Number of "100" hits
        In taiko: number of "good" hits
        In catch: number of "drop" hits
    count300 : int
        Number of "300" hits
        In taiko: number of "great" hits
        In catch: number of "fruit" hits
    countmiss : int
        Number of misses
        In catch: number of "fruit" or "drop" misses
    countkatu : int
        Number of "katu" sections (only 100s and 300s)
        In taiko: number of "double good" hits
        In mania: number of "200" hits
        In catch: number of "droplet" misses
    countgeki : int
        Number of "geki" sections (only 300s)
        In taiko: number of "double great" hits
        In mania: number of "rainbow 300" hits
    perfect : bool
        If the play is a full combo (maxcombo is maximal)
    user_id : int
        ID of user who played.
    rank :  str
        Letter rank achieved

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>
    """
    score = Attribute(int)
    maxcombo = Attribute(int)
    count50 = Attribute(int)
    count100 = Attribute(int)
    count300 = Attribute(int)
    countmiss = Attribute(int)
    countkatu = Attribute(int)
    countgeki = Attribute(int)
    perfect = Attribute(PreProcessInt(bool))
    user_id = Attribute(int)
    rank = Attribute(str)

    def accuracy(self, mode: OsuMode):
        """Calculated accuracy.

        See Also
        --------
        <https://osu.ppy.sh/help/wiki/Accuracy>
        """
        if mode is OsuMode.osu:
            return (
                (6 * self.count300 + 2 * self.count100 + self.count50) /
                (6 * (self.count300 + self.count100 + self.count50 + self.countmiss)))
        if mode is OsuMode.taiko:
            return (
                (self.count300 + self.countgeki + (0.5*(self.count100 + self.countkatu))) /
                (self.count300 + self.countgeki + self.count100 + self.countkatu + self.countmiss))
        if mode is OsuMode.mania:
            return (
                (6 * (self.countgeki + self.count300) + 4 * self.countkatu + 2 * self.count100 + self.count50) /
                (6 * (self.countgeki + self.count300 + self.countkatu + self.count100 + self.count50 + self.countmiss)))
        if mode is OsuMode.ctb:
            return (
                (self.count50 + self.count100 + self.count300) /
                (self.count50 + self.count100 + self.count300 + self.countmiss + self.countkatu))

class TeamScore(Score):
    """Class representing a score in a multiplayer team game.

    See :class:`Score`

    Attributes
    -----------
    slot : int
        Which multiplayer slot the player was in.
    team : int
        Which multiplayer team the player was in.
    passed : bool
        If the score is passing.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>
    """
    slot = Attribute(int)
    team = Attribute(int)
    passed = Attribute(PreProcessInt(bool), name="pass")

    def __repr__(self):
        return "<{0.__module__}.TeamScore user_id={0.user_id} team={0.team}>".format(self)

class RecentScore(Score):
    """Class representing a recent score.

    See :class:`Score`

    Attributes
    -----------
    beatmap_id : int
        Beatmap the score is for.
    enabled_mods : :class:`osuapi.enums.OsuMod`
        Enabled modifiers
    date : datetime
        When the score was played.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>
    """
    beatmap_id = Attribute(int)
    enabled_mods = Attribute(PreProcessInt(OsuMod))
    date = Attribute(DateConverter)

    def __repr__(self):
        return "<{0.__module__}.SoloScore user_id={0.user_id} beatmap_id={0.beatmap_id} date={0.date}>".format(self)

class SoloScore(Score):
    """Class representing a score in singleplayer.

    See :class:`Score`

    Attributes
    -----------
    beatmap_id : int
        Beatmap the score is for.
    pp : Optional[float]
        How much PP the score is worth, or None if not eligible for PP.
    enabled_mods : :class:`osuapi.enums.OsuMod`
        Enabled modifiers
    date : datetime
        When the score was played.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>
    """
    beatmap_id = Attribute(int)
    pp = Attribute(Nullable(float))
    enabled_mods = Attribute(PreProcessInt(OsuMod))
    date = Attribute(DateConverter)

    def __repr__(self):
        return "<{0.__module__}.SoloScore user_id={0.user_id} beatmap_id={0.beatmap_id} date={0.date}>".format(self)

class BeatmapScore(Score):
    """Class representing a score attached to a beatmap.

    See :class:`Score`

    Attributes
    -----------
    username : str
        Name of user.
    pp : Optional[float]
        How much PP the score is worth, or None if not eligible for PP.
    enabled_mods : :class:`osuapi.enums.OsuMod`
        Enabled modifiers
    date : datetime
        When the score was played.
    score_id : int
        ID of score.
    replay_available : bool
        If a replay is available.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>
    """
    username = Attribute(str)
    pp = Attribute(Nullable(float))
    enabled_mods = Attribute(PreProcessInt(OsuMod))
    date = Attribute(DateConverter)
    score_id = Attribute(int)
    replay_available = Attribute(PreProcessInt(bool))

    def __repr__(self):
        return "<{0.__module__}.BeatmapScore user_id={0.user_id} score_id={0.score_id} date={0.date}>".format(self)

class UserEvent(AttributeModel):
    """Class representing individual user events.

    Attributes
    -----------
    display_html : str
        HTML for the event.
    beatmap_id : Optional[int]
        Beatmap this event occured on, or None if the event has no beatmap.
    beatmapset_id : Optional[int]
        Beatmap set this event occured on, or None if the event has no beatmap.
    date : datetime
        Date this event occured.
    epicfactor : int
        Epic factor (between 1 and 32)
    """

    display_html = Attribute(str)
    beatmap_id = Attribute(Nullable(int))
    beatmapset_id = Attribute(Nullable(int))
    date = Attribute(DateConverter)
    epicfactor = Attribute(int)

    def __repr__(self):
        return "<{0.__module__}.UserEvent beatmap_id={0.beatmap_id} date={0.date} epicfactor={0.epicfactor}>".format(self)

class User(AttributeModel):
    """Class representing a user.

    Attributes
    -----------
    user_id : int
        User's unique identifier.
    username : str
        User's name.
    count300 : int
        Career total of "300" hits.
    count100 : int
        Career total of "100" hits.
    count50 : int
        Career total of "50" hits.
    playcount : int
        Career total play count.
    ranked_score : int
        Total sum of the best scores from all the ranked beatmaps played online.
    total_score : int
        Total sum of all scores on ranked beatmaps, including failed trails.
    pp_rank : int
        Global ranking place.
    level: float
        User's level
    pp_raw: float
        User's performance points
    total_seconds_played: int
        User's total playtime
    accuracy : float
        Weighted average of accuracy on top plays.
    count_rank_ssh : int
        Career total of SSH ranks.
    count_rank_ss : int
        Career total of SS ranks.
    count_rank_sh : int
        Career total of SH ranks.
    count_rank_s : int
        Career total of S ranks.
    count_rank_a : int
        Career total of A ranks.
    country : str
        Country the user is registered to.
    pp_country_rank : int
        Country ranking place.
    events : list[dict]
        Information about recent "interesting" events.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>

    """
    user_id = Attribute(int)
    username = Attribute(str)
    count300 = Attribute(Nullable(int))
    count100 = Attribute(Nullable(int))
    count50 = Attribute(Nullable(int))
    playcount = Attribute(Nullable(int))
    ranked_score = Attribute(Nullable(int))
    total_score = Attribute(Nullable(int))
    pp_rank = Attribute(Nullable(int))
    level = Attribute(Nullable(float))
    pp_raw = Attribute(Nullable(float))
    total_seconds_played = Attribute(Nullable(int))
    accuracy = Attribute(Nullable(float))
    count_rank_ssh = Attribute(Nullable(int))
    count_rank_ss = Attribute(Nullable(int))
    count_rank_sh = Attribute(Nullable(int))
    count_rank_s = Attribute(Nullable(int))
    count_rank_a = Attribute(Nullable(int))
    country = Attribute(str)
    pp_country_rank = Attribute(int)
    events = Attribute(JsonList(UserEvent))
    join_date = Attribute(DateConverter)

    @property
    def total_hits(self):
        return self.count300 + self.count100 + self.count50

    def __repr__(self):
        return "<{0.__module__}.User username={0.username} user_id={0.user_id}>".format(self)

    def __str__(self):
        return self.username

class Beatmap(AttributeModel):
    """Class representing a beatmap

    Attributes
    -----------
    approved : BeatmapStatus
        Whether or not the map has been ranked.
    approved_date : Optional[datetime]
        When the beatmap was ranked, or None.
    last_update : datetime
        Last time the map was updated.
    artist : str
        Music metadata.
    beatmap_id : int
        Unique identifier for beatmap.
    beatmapset_id : int
        Unique identifier for set this beatmap belongs to.
    bpm : float
        Speed of map in beats per minute.
    creator : str
        Username of map creator.
    creator_id: int
        ID of the map creator.
    difficultyrating : float
        Star rating of a map.
    diff_size : float
        Circle Size. (CS)
    diff_overall : float
        Overall Difficulty. (OD)
    diff_approach : float
        Approach rate. (AR)
    diff_drain : float
        Health Drain (HP)
    hit_length : int
        Playable time in seconds. (Drain time)
    source : str
        Source of the music
    genre_id : :class:`osuapi.enums.BeatmapGenre`
        Genre of the music.
    language_id : :class:`osuapi.enums.BeatmapLanguage`
        Language of the music.
    title : str
        Title of the song.
    total_length : int
        Total song length in seconds.
    version : str
        Difficulty name.
    file_md5 : str
        md5 hash of map.
    mode : :class:`osuapi.enums.OsuMode`
        Game mode for the map.
    tags : str
        Space delimited tags for the map.
    favourite_count : int
        Number of users that have favorited this map.
    playcount : int
        Number of times this map has been played (including fails)/
    passcount : int
        Number of times this map has been passed.
    max_combo : Optional[int]
        Maximum possible combo.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Beatmaps>
    """
    approved = Attribute(PreProcessInt(BeatmapStatus))
    approved_date = Attribute(Nullable(DateConverter))
    last_update = Attribute(DateConverter)
    artist = Attribute(str)
    beatmap_id = Attribute(int)
    beatmapset_id = Attribute(int)
    bpm = Attribute(float)
    creator = Attribute(str)
    creator_id = Attribute(int)
    difficultyrating = Attribute(float)
    diff_size = Attribute(float)
    diff_overall = Attribute(float)
    diff_approach = Attribute(float)
    diff_drain = Attribute(float)
    hit_length = Attribute(int)
    source = Attribute(str)
    genre_id = Attribute(PreProcessInt(BeatmapGenre))
    language_id = Attribute(PreProcessInt(BeatmapLanguage))
    title = Attribute(str)
    total_length = Attribute(int)
    version = Attribute(str)
    file_md5 = Attribute(str)
    mode = Attribute(PreProcessInt(OsuMode))
    tags = Attribute(str)
    favourite_count = Attribute(int)
    playcount = Attribute(int)
    passcount = Attribute(int)
    max_combo = Attribute(Nullable(int))

    def __repr__(self):
        return "<{0.__module__}.Beatmap title={0.title} creator={0.creator} id={0.beatmap_id}>".format(self)

    @property
    def url(self):
        return "https://osu.ppy.sh/b/{0.beatmap_id}".format(self)

    @property
    def set_url(self):
        return "https://osu.ppy.sh/s/{0.beatmapset_id}".format(self)

class MatchMetadata(AttributeModel):
    """Class representing info about a match.

    Attributes
    -----------
    match_id : int
        Unique identifier for this match.
    name : str
        Name of the match when it was first created.
    start_time : datetime
        When the match was created.
    end_time : Optional[datetime]
        When the match was ended, or None.
    """
    match_id = Attribute(int)
    name = Attribute(str)
    start_time = Attribute(DateConverter)
    end_time = Attribute(Nullable(DateConverter))

    def __repr__(self):
        return "<{0.__module__}.MatchMetadata id={0.match_id} name={0.name} start_time={0.start_time}>".format(self)

class Game(AttributeModel):
    """Class representing an individual multiplayer game.

    Attributes
    -----------
    game_id : int
        Unique identifier for this game.
    start_time : datetime
        When the game started.
    end_time : datetime
        When the game ended.
    beatmap_id : int
        Beatmap played.
    play_mode : :class:`osuapi.enums.OsuMode`
        Game mode.
    match_type
        Not really sure...
    scoring_type : :class:`osuapi.enums.ScoringType`
        Scoring type of game.
    team_type : :class:`osuapi.enums.TeamType`
        Team type of the game.
    mods : :class:`osuapi.enums.OsuMod`
        Modifiers enabled for all players.
    scores : list[:class:`TeamScore`]
        List of scores for all players.
    """
    game_id = Attribute(int)
    start_time = Attribute(DateConverter)
    end_time = Attribute(DateConverter)
    beatmap_id = Attribute(int)
    play_mode = Attribute(PreProcessInt(OsuMode))
    match_type = Attribute(str)  # not sure what this is?
    scoring_type = Attribute(PreProcessInt(ScoringType))
    team_type = Attribute(PreProcessInt(TeamType))
    mods = Attribute(PreProcessInt(OsuMod))
    scores = Attribute(JsonList(TeamScore))

    def __repr__(self):
        return "<{0.__module__}.Game id={0.game_id} beatmap_id={0.beatmap_id} start_time={0.start_time}".format(self)

class Match(AttributeModel):
    """Class representing a match's info and collection of games."""
    match = Attribute(MatchMetadata)
    games = Attribute(JsonList(Game))
<commit_msg>add aim, speed, strain to Beatmap<commit_after>"""Different classes to parse dicts/lists returned from json into meaningful data objects."""

from .enums import *
from .dictmodel import AttributeModel, Attribute, JsonList, Nullable, PreProcessInt, DateConverter

class Score(AttributeModel):
    """Abstract class representing a score.

    Attributes
    -----------
    score : int
        The score value
    maxcombo : int
        Largest combo achieved
    count50 : int
        Number of "50" hits.
        In catch: number of "droplet" hits
    count100 : int
        Number of "100" hits
        In taiko: number of "good" hits
        In catch: number of "drop" hits
    count300 : int
        Number of "300" hits
        In taiko: number of "great" hits
        In catch: number of "fruit" hits
    countmiss : int
        Number of misses
        In catch: number of "fruit" or "drop" misses
    countkatu : int
        Number of "katu" sections (only 100s and 300s)
        In taiko: number of "double good" hits
        In mania: number of "200" hits
        In catch: number of "droplet" misses
    countgeki : int
        Number of "geki" sections (only 300s)
        In taiko: number of "double great" hits
        In mania: number of "rainbow 300" hits
    perfect : bool
        If the play is a full combo (maxcombo is maximal)
    user_id : int
        ID of user who played.
    rank :  str
        Letter rank achieved

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>
    """
    score = Attribute(int)
    maxcombo = Attribute(int)
    count50 = Attribute(int)
    count100 = Attribute(int)
    count300 = Attribute(int)
    countmiss = Attribute(int)
    countkatu = Attribute(int)
    countgeki = Attribute(int)
    perfect = Attribute(PreProcessInt(bool))
    user_id = Attribute(int)
    rank = Attribute(str)

    def accuracy(self, mode: OsuMode):
        """Calculated accuracy.

        See Also
        --------
        <https://osu.ppy.sh/help/wiki/Accuracy>
        """
        if mode is OsuMode.osu:
            return (
                (6 * self.count300 + 2 * self.count100 + self.count50) /
                (6 * (self.count300 + self.count100 + self.count50 + self.countmiss)))
        if mode is OsuMode.taiko:
            return (
                (self.count300 + self.countgeki + (0.5*(self.count100 + self.countkatu))) /
                (self.count300 + self.countgeki + self.count100 + self.countkatu + self.countmiss))
        if mode is OsuMode.mania:
            return (
                (6 * (self.countgeki + self.count300) + 4 * self.countkatu + 2 * self.count100 + self.count50) /
                (6 * (self.countgeki + self.count300 + self.countkatu + self.count100 + self.count50 + self.countmiss)))
        if mode is OsuMode.ctb:
            return (
                (self.count50 + self.count100 + self.count300) /
                (self.count50 + self.count100 + self.count300 + self.countmiss + self.countkatu))

class TeamScore(Score):
    """Class representing a score in a multiplayer team game.

    See :class:`Score`

    Attributes
    -----------
    slot : int
        Which multiplayer slot the player was in.
    team : int
        Which multiplayer team the player was in.
    passed : bool
        If the score is passing.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>
    """
    slot = Attribute(int)
    team = Attribute(int)
    passed = Attribute(PreProcessInt(bool), name="pass")

    def __repr__(self):
        return "<{0.__module__}.TeamScore user_id={0.user_id} team={0.team}>".format(self)

class RecentScore(Score):
    """Class representing a recent score.

    See :class:`Score`

    Attributes
    -----------
    beatmap_id : int
        Beatmap the score is for.
    enabled_mods : :class:`osuapi.enums.OsuMod`
        Enabled modifiers
    date : datetime
        When the score was played.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>
    """
    beatmap_id = Attribute(int)
    enabled_mods = Attribute(PreProcessInt(OsuMod))
    date = Attribute(DateConverter)

    def __repr__(self):
        return "<{0.__module__}.SoloScore user_id={0.user_id} beatmap_id={0.beatmap_id} date={0.date}>".format(self)

class SoloScore(Score):
    """Class representing a score in singleplayer.

    See :class:`Score`

    Attributes
    -----------
    beatmap_id : int
        Beatmap the score is for.
    pp : Optional[float]
        How much PP the score is worth, or None if not eligible for PP.
    enabled_mods : :class:`osuapi.enums.OsuMod`
        Enabled modifiers
    date : datetime
        When the score was played.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>
    """
    beatmap_id = Attribute(int)
    pp = Attribute(Nullable(float))
    enabled_mods = Attribute(PreProcessInt(OsuMod))
    date = Attribute(DateConverter)

    def __repr__(self):
        return "<{0.__module__}.SoloScore user_id={0.user_id} beatmap_id={0.beatmap_id} date={0.date}>".format(self)

class BeatmapScore(Score):
    """Class representing a score attached to a beatmap.

    See :class:`Score`

    Attributes
    -----------
    username : str
        Name of user.
    pp : Optional[float]
        How much PP the score is worth, or None if not eligible for PP.
    enabled_mods : :class:`osuapi.enums.OsuMod`
        Enabled modifiers
    date : datetime
        When the score was played.
    score_id : int
        ID of score.
    replay_available : bool
        If a replay is available.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>
    """
    username = Attribute(str)
    pp = Attribute(Nullable(float))
    enabled_mods = Attribute(PreProcessInt(OsuMod))
    date = Attribute(DateConverter)
    score_id = Attribute(int)
    replay_available = Attribute(PreProcessInt(bool))

    def __repr__(self):
        return "<{0.__module__}.BeatmapScore user_id={0.user_id} score_id={0.score_id} date={0.date}>".format(self)

class UserEvent(AttributeModel):
    """Class representing individual user events.

    Attributes
    -----------
    display_html : str
        HTML for the event.
    beatmap_id : Optional[int]
        Beatmap this event occured on, or None if the event has no beatmap.
    beatmapset_id : Optional[int]
        Beatmap set this event occured on, or None if the event has no beatmap.
    date : datetime
        Date this event occured.
    epicfactor : int
        Epic factor (between 1 and 32)
    """

    display_html = Attribute(str)
    beatmap_id = Attribute(Nullable(int))
    beatmapset_id = Attribute(Nullable(int))
    date = Attribute(DateConverter)
    epicfactor = Attribute(int)

    def __repr__(self):
        return "<{0.__module__}.UserEvent beatmap_id={0.beatmap_id} date={0.date} epicfactor={0.epicfactor}>".format(self)

class User(AttributeModel):
    """Class representing a user.

    Attributes
    -----------
    user_id : int
        User's unique identifier.
    username : str
        User's name.
    count300 : int
        Career total of "300" hits.
    count100 : int
        Career total of "100" hits.
    count50 : int
        Career total of "50" hits.
    playcount : int
        Career total play count.
    ranked_score : int
        Total sum of the best scores from all the ranked beatmaps played online.
    total_score : int
        Total sum of all scores on ranked beatmaps, including failed trails.
    pp_rank : int
        Global ranking place.
    level: float
        User's level
    pp_raw: float
        User's performance points
    total_seconds_played: int
        User's total playtime
    accuracy : float
        Weighted average of accuracy on top plays.
    count_rank_ssh : int
        Career total of SSH ranks.
    count_rank_ss : int
        Career total of SS ranks.
    count_rank_sh : int
        Career total of SH ranks.
    count_rank_s : int
        Career total of S ranks.
    count_rank_a : int
        Career total of A ranks.
    country : str
        Country the user is registered to.
    pp_country_rank : int
        Country ranking place.
    events : list[dict]
        Information about recent "interesting" events.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Score>

    """
    user_id = Attribute(int)
    username = Attribute(str)
    count300 = Attribute(Nullable(int))
    count100 = Attribute(Nullable(int))
    count50 = Attribute(Nullable(int))
    playcount = Attribute(Nullable(int))
    ranked_score = Attribute(Nullable(int))
    total_score = Attribute(Nullable(int))
    pp_rank = Attribute(Nullable(int))
    level = Attribute(Nullable(float))
    pp_raw = Attribute(Nullable(float))
    total_seconds_played = Attribute(Nullable(int))
    accuracy = Attribute(Nullable(float))
    count_rank_ssh = Attribute(Nullable(int))
    count_rank_ss = Attribute(Nullable(int))
    count_rank_sh = Attribute(Nullable(int))
    count_rank_s = Attribute(Nullable(int))
    count_rank_a = Attribute(Nullable(int))
    country = Attribute(str)
    pp_country_rank = Attribute(int)
    events = Attribute(JsonList(UserEvent))
    join_date = Attribute(DateConverter)

    @property
    def total_hits(self):
        return self.count300 + self.count100 + self.count50

    def __repr__(self):
        return "<{0.__module__}.User username={0.username} user_id={0.user_id}>".format(self)

    def __str__(self):
        return self.username

class Beatmap(AttributeModel):
    """Class representing a beatmap

    Attributes
    -----------
    approved : BeatmapStatus
        Whether or not the map has been ranked.
    approved_date : Optional[datetime]
        When the beatmap was ranked, or None.
    last_update : datetime
        Last time the map was updated.
    artist : str
        Music metadata.
    beatmap_id : int
        Unique identifier for beatmap.
    beatmapset_id : int
        Unique identifier for set this beatmap belongs to.
    bpm : float
        Speed of map in beats per minute.
    creator : str
        Username of map creator.
    creator_id: int
        ID of the map creator.
    difficultyrating : float
        Star rating of a map.
    diff_aim : float
        Aim portion of difficulty
    diff_speed : float
        Speed portion of difficulty
    diff_strain : float
        Strain portion of difficulty
    diff_size : float
        Circle Size. (CS)
    diff_overall : float
        Overall Difficulty. (OD)
    diff_approach : float
        Approach rate. (AR)
    diff_drain : float
        Health Drain (HP)
    hit_length : int
        Playable time in seconds. (Drain time)
    source : str
        Source of the music
    genre_id : :class:`osuapi.enums.BeatmapGenre`
        Genre of the music.
    language_id : :class:`osuapi.enums.BeatmapLanguage`
        Language of the music.
    title : str
        Title of the song.
    total_length : int
        Total song length in seconds.
    version : str
        Difficulty name.
    file_md5 : str
        md5 hash of map.
    mode : :class:`osuapi.enums.OsuMode`
        Game mode for the map.
    tags : str
        Space delimited tags for the map.
    favourite_count : int
        Number of users that have favorited this map.
    playcount : int
        Number of times this map has been played (including fails)/
    passcount : int
        Number of times this map has been passed.
    max_combo : Optional[int]
        Maximum possible combo.

    See Also
    ---------
    <https://osu.ppy.sh/wiki/Beatmaps>
    """
    approved = Attribute(PreProcessInt(BeatmapStatus))
    approved_date = Attribute(Nullable(DateConverter))
    last_update = Attribute(DateConverter)
    artist = Attribute(str)
    beatmap_id = Attribute(int)
    beatmapset_id = Attribute(int)
    bpm = Attribute(float)
    creator = Attribute(str)
    creator_id = Attribute(int)
    difficultyrating = Attribute(float)
    diff_aim = Attribute(float)
    diff_speed = Attribute(float)
    diff_strain = Attribute(float)
    diff_size = Attribute(float)
    diff_overall = Attribute(float)
    diff_approach = Attribute(float)
    diff_drain = Attribute(float)
    hit_length = Attribute(int)
    source = Attribute(str)
    genre_id = Attribute(PreProcessInt(BeatmapGenre))
    language_id = Attribute(PreProcessInt(BeatmapLanguage))
    title = Attribute(str)
    total_length = Attribute(int)
    version = Attribute(str)
    file_md5 = Attribute(str)
    mode = Attribute(PreProcessInt(OsuMode))
    tags = Attribute(str)
    favourite_count = Attribute(int)
    playcount = Attribute(int)
    passcount = Attribute(int)
    max_combo = Attribute(Nullable(int))

    def __repr__(self):
        return "<{0.__module__}.Beatmap title={0.title} creator={0.creator} id={0.beatmap_id}>".format(self)

    @property
    def url(self):
        return "https://osu.ppy.sh/b/{0.beatmap_id}".format(self)

    @property
    def set_url(self):
        return "https://osu.ppy.sh/s/{0.beatmapset_id}".format(self)

class MatchMetadata(AttributeModel):
    """Class representing info about a match.

    Attributes
    -----------
    match_id : int
        Unique identifier for this match.
    name : str
        Name of the match when it was first created.
    start_time : datetime
        When the match was created.
    end_time : Optional[datetime]
        When the match was ended, or None.
    """
    match_id = Attribute(int)
    name = Attribute(str)
    start_time = Attribute(DateConverter)
    end_time = Attribute(Nullable(DateConverter))

    def __repr__(self):
        return "<{0.__module__}.MatchMetadata id={0.match_id} name={0.name} start_time={0.start_time}>".format(self)

class Game(AttributeModel):
    """Class representing an individual multiplayer game.

    Attributes
    -----------
    game_id : int
        Unique identifier for this game.
    start_time : datetime
        When the game started.
    end_time : datetime
        When the game ended.
    beatmap_id : int
        Beatmap played.
    play_mode : :class:`osuapi.enums.OsuMode`
        Game mode.
    match_type
        Not really sure...
    scoring_type : :class:`osuapi.enums.ScoringType`
        Scoring type of game.
    team_type : :class:`osuapi.enums.TeamType`
        Team type of the game.
    mods : :class:`osuapi.enums.OsuMod`
        Modifiers enabled for all players.
    scores : list[:class:`TeamScore`]
        List of scores for all players.
    """
    game_id = Attribute(int)
    start_time = Attribute(DateConverter)
    end_time = Attribute(DateConverter)
    beatmap_id = Attribute(int)
    play_mode = Attribute(PreProcessInt(OsuMode))
    match_type = Attribute(str)  # not sure what this is?
    scoring_type = Attribute(PreProcessInt(ScoringType))
    team_type = Attribute(PreProcessInt(TeamType))
    mods = Attribute(PreProcessInt(OsuMod))
    scores = Attribute(JsonList(TeamScore))

    def __repr__(self):
        return "<{0.__module__}.Game id={0.game_id} beatmap_id={0.beatmap_id} start_time={0.start_time}".format(self)

class Match(AttributeModel):
    """Class representing a match's info and collection of games."""
    match = Attribute(MatchMetadata)
    games = Attribute(JsonList(Game))
<|endoftext|>


<commit_before># gozerlib/utils/lazydict.py
#
# thnx to maze

""" a lazydict allows dotted access to a dict .. dict.key. """

## gozerlib imports

from gozerlib.utils.exception import handle_exception
from gozerlib.errors import PropertyIgnored

## simplejson imports

from simplejson import loads, dumps

## basic imports

from  xml.sax.saxutils import unescape
import copy
import logging
import uuid
import types

## defines

jsontypes = [types.StringType, types.UnicodeType, types.DictType, types.ListType, types.IntType]

defaultignore = ['body', 'subs', 'args', 'rest', 'cfg', 'pass', 'password', 'fsock', 'sock', 'handlers', 'users', 'plugins', 'outqueue', 'inqueue']

cpy = copy.deepcopy

## functions

def checkignore(name, ignore):
    if name.startswith('_'):
        return True
    for item in ignore:
        if item == name:
            logging.warn("lazydict - ignoring on %s" % name)
            return True
    return False

def dumpelement(element, withtypes=False):
    """ check each attribute of element whether it is dumpable. """
    new = {}
    for name in element:
        if checkignore(name, defaultignore):
            continue
        if not element[name]:
            continue
        try:
            dumps(element[name])
            new[name] = element[name]
        except TypeError:
            if type(element) not in jsontypes:
                if withtypes:
                    new[name] = unicode(type(element))
            else:
                new[name] = dumpelement(element[name])
    return new

## classes

class LazyDict(dict):

    """ lazy dict allows dotted access to a dict """

    def __deepcopy__(self, a):
        return LazyDict(a)

    def __getattr__(self, attr, default=None):
        """ get attribute. """
        if not self.has_key(attr):
            self[attr] = default
            #logging.warning("setting default of %s to %s" % (attr, default))

        return self[attr]

    def __setattr__(self, attr, value):
        """ set attribute. """
        self[attr] = value

    def dostring(self):
        """ return a string representation of the dict """
        res = ""
        cp = dict(self)
        for item, value in cp.iteritems():
            res += "%r=%r " % (item, value)

        return res

    def tojson(self):
        return dumps(dumpelement(self))

    def dump(self, attribs=[]):
        logging.debug("lazydict - dumping - %s" %  type(self))
        return dumpelement(self)

    def load(self, input):
        """ load from json string. """
        try:
            temp = loads(input)
        except ValueError:
            handle_exception()
            logging.error("lazydict - can't decode %s" % input)
            return self
        if type(temp) != dict:
            logging.error("lazydict - %s is not a dict" % str(temp))
            return self
        self.update(temp)
        return self
<commit_msg>remove more elements from dumpelement<commit_after># gozerlib/utils/lazydict.py
#
# thnx to maze

""" a lazydict allows dotted access to a dict .. dict.key. """

## gozerlib imports

from gozerlib.utils.exception import handle_exception
from gozerlib.errors import PropertyIgnored

## simplejson imports

from simplejson import loads, dumps

## basic imports

from  xml.sax.saxutils import unescape
import copy
import logging
import uuid
import types

## defines

jsontypes = [types.StringType, types.UnicodeType, types.DictType, types.ListType, types.IntType]

defaultignore = ['origtxt', 'body', 'subelements', 'args', 'rest', 'cfg', 'pass', 'password', 'fsock', 'sock', 'handlers', 'users', 'plugins', 'outqueue', 'inqueue']

cpy = copy.deepcopy

## functions

def checkignore(name, ignore):
    if name.startswith('_'):
        return True
    for item in ignore:
        if item == name:
            logging.warn("lazydict - ignoring on %s" % name)
            return True
    return False

def dumpelement(element, withtypes=False):
    """ check each attribute of element whether it is dumpable. """
    new = {}
    for name in element:
        if checkignore(name, defaultignore):
            continue
        if not element[name]:
            continue
        try:
            dumps(element[name])
            new[name] = element[name]
        except TypeError:
            if type(element) not in jsontypes:
                if withtypes:
                    new[name] = unicode(type(element))
            else:
                new[name] = dumpelement(element[name])
    return new

## classes

class LazyDict(dict):

    """ lazy dict allows dotted access to a dict """

    def __deepcopy__(self, a):
        return LazyDict(a)

    def __getattr__(self, attr, default=None):
        """ get attribute. """
        if not self.has_key(attr):
            self[attr] = default
            #logging.warning("setting default of %s to %s" % (attr, default))

        return self[attr]

    def __setattr__(self, attr, value):
        """ set attribute. """
        self[attr] = value

    def dostring(self):
        """ return a string representation of the dict """
        res = ""
        cp = dict(self)
        for item, value in cp.iteritems():
            res += "%r=%r " % (item, value)

        return res

    def tojson(self):
        return dumps(dumpelement(self))

    def dump(self, attribs=[]):
        logging.debug("lazydict - dumping - %s" %  type(self))
        return dumpelement(self)

    def load(self, input):
        """ load from json string. """
        try:
            temp = loads(input)
        except ValueError:
            handle_exception()
            logging.error("lazydict - can't decode %s" % input)
            return self
        if type(temp) != dict:
            logging.error("lazydict - %s is not a dict" % str(temp))
            return self
        self.update(temp)
        return self
<|endoftext|>


<commit_before>"Implements Model"
from collections import defaultdict
from .costed import CostedConstraintSet
from ..varkey import VarKey
from ..nomials import Monomial
from .prog_factories import _progify_fctry, _solve_fctry
from ..geometric_program import GeometricProgram
from .signomial_program import SignomialProgram
from .link import LinkConstraint
from ..keydict import KeyDict
from .. import SignomialsEnabled

class Model(CostedConstraintSet):
    """Symbolic representation of an optimization problem.

    The Model class is used both directly to create models with constants and
    sweeps, and indirectly inherited to create custom model classes.

    Arguments
    ---------
    cost : Posynomial (optional)
        Defaults to `Monomial(1)`.

    constraints : ConstraintSet or list of constraints (optional)
        Defaults to an empty list.

    substitutions : dict (optional)
        This dictionary will be substituted into the problem before solving,
        and also allows the declaration of sweeps and linked sweeps.

    name : str (optional)
        Allows "naming" a model in a way similar to inherited instances,
        and overrides the inherited name if there is one.

    Attributes with side effects
    ----------------------------
    `program` is set during a solve
    `solution` is set at the end of a solve
    """
    _nums = defaultdict(int)
    name = None
    num = None
    program = None
    solution = None

    def __init__(self, cost=None, constraints=None,
                 substitutions=None, name=None):
        if hasattr(self, "setup"):
            # temporarily fail gracefully for backwards compatibility
            raise RuntimeWarning(
                "setup methods are no longer used in GPkit. "
                "To initialize a model, rename your setup method as "
                "__init__(self, **kwargs) and have it call "
                "Model.__init__(self, cost, constraints, **kwargs) at the end.")
        cost = cost if cost is not None else Monomial(1)
        constraints = constraints if constraints is not None else []
        CostedConstraintSet.__init__(self, cost, constraints, substitutions)
        if self.__class__.__name__ != "Model" and not name:
            name = self.__class__.__name__
        if name:
            self.name = name
            self.num = Model._nums[name]
            Model._nums[name] += 1
            self._add_modelname_tovars(self.name, self.num)

    gp = _progify_fctry(GeometricProgram)
    sp = _progify_fctry(SignomialProgram)
    solve = _solve_fctry(_progify_fctry(GeometricProgram, "solve"))
    localsolve = _solve_fctry(_progify_fctry(SignomialProgram, "localsolve"))

    def link(self, other, include_only=None, exclude=None):
        "Connects this model with a set of constraints"
        lc = LinkConstraint([self, other], include_only, exclude)
        cost = self.cost.sub(lc.linked)
        return Model(cost, [lc], lc.substitutions)

    def zero_lower_unbounded_variables(self):
        "Recursively substitutes 0 for variables that lack a lower bound"
        zeros = True
        while zeros:
            # pylint: disable=no-member
            bounds = self.gp(verbosity=0).missingbounds
            zeros = {var: 0 for var, bound in bounds.items()
                     if bound == "lower"}
            self.substitutions.update(zeros)

    def _add_modelname_tovars(self, name, num):
        add_model_subs = KeyDict()
        for vk in self.varkeys:
            descr = dict(vk.descr)
            descr["models"] = descr.pop("models", []) + [name]
            descr["modelnums"] = descr.pop("modelnums", []) + [num]
            newvk = VarKey(**descr)
            add_model_subs[vk] = newvk
            if vk in self.substitutions:
                self.substitutions[newvk] = self.substitutions[vk]
                del self.substitutions[vk]
        with SignomialsEnabled():  # since we're just substituting varkeys.
            self.subinplace(add_model_subs)

    def subconstr_str(self, excluded=None):
        "The collapsed appearance of a ConstraintBase"
        if self.name:
            return "%s_%s" % (self.name, self.num)

    def subconstr_tex(self, excluded=None):
        "The collapsed appearance of a ConstraintBase"
        if self.name:
            return "%s_{%s}" % (self.name, self.num)
<commit_msg>fix latex printing<commit_after>"Implements Model"
from collections import defaultdict
from .costed import CostedConstraintSet
from ..varkey import VarKey
from ..nomials import Monomial
from .prog_factories import _progify_fctry, _solve_fctry
from ..geometric_program import GeometricProgram
from .signomial_program import SignomialProgram
from .link import LinkConstraint
from ..keydict import KeyDict
from .. import SignomialsEnabled

class Model(CostedConstraintSet):
    """Symbolic representation of an optimization problem.

    The Model class is used both directly to create models with constants and
    sweeps, and indirectly inherited to create custom model classes.

    Arguments
    ---------
    cost : Posynomial (optional)
        Defaults to `Monomial(1)`.

    constraints : ConstraintSet or list of constraints (optional)
        Defaults to an empty list.

    substitutions : dict (optional)
        This dictionary will be substituted into the problem before solving,
        and also allows the declaration of sweeps and linked sweeps.

    name : str (optional)
        Allows "naming" a model in a way similar to inherited instances,
        and overrides the inherited name if there is one.

    Attributes with side effects
    ----------------------------
    `program` is set during a solve
    `solution` is set at the end of a solve
    """
    _nums = defaultdict(int)
    name = None
    num = None
    program = None
    solution = None

    def __init__(self, cost=None, constraints=None,
                 substitutions=None, name=None):
        if hasattr(self, "setup"):
            # temporarily fail gracefully for backwards compatibility
            raise RuntimeWarning(
                "setup methods are no longer used in GPkit. "
                "To initialize a model, rename your setup method as "
                "__init__(self, **kwargs) and have it call "
                "Model.__init__(self, cost, constraints, **kwargs) at the end.")
        cost = cost if cost is not None else Monomial(1)
        constraints = constraints if constraints is not None else []
        CostedConstraintSet.__init__(self, cost, constraints, substitutions)
        if self.__class__.__name__ != "Model" and not name:
            name = self.__class__.__name__
        if name:
            self.name = name
            self.num = Model._nums[name]
            Model._nums[name] += 1
            self._add_modelname_tovars(self.name, self.num)

    gp = _progify_fctry(GeometricProgram)
    sp = _progify_fctry(SignomialProgram)
    solve = _solve_fctry(_progify_fctry(GeometricProgram, "solve"))
    localsolve = _solve_fctry(_progify_fctry(SignomialProgram, "localsolve"))

    def link(self, other, include_only=None, exclude=None):
        "Connects this model with a set of constraints"
        lc = LinkConstraint([self, other], include_only, exclude)
        cost = self.cost.sub(lc.linked)
        return Model(cost, [lc], lc.substitutions)

    def zero_lower_unbounded_variables(self):
        "Recursively substitutes 0 for variables that lack a lower bound"
        zeros = True
        while zeros:
            # pylint: disable=no-member
            bounds = self.gp(verbosity=0).missingbounds
            zeros = {var: 0 for var, bound in bounds.items()
                     if bound == "lower"}
            self.substitutions.update(zeros)

    def _add_modelname_tovars(self, name, num):
        add_model_subs = KeyDict()
        for vk in self.varkeys:
            descr = dict(vk.descr)
            descr["models"] = descr.pop("models", []) + [name]
            descr["modelnums"] = descr.pop("modelnums", []) + [num]
            newvk = VarKey(**descr)
            add_model_subs[vk] = newvk
            if vk in self.substitutions:
                self.substitutions[newvk] = self.substitutions[vk]
                del self.substitutions[vk]
        with SignomialsEnabled():  # since we're just substituting varkeys.
            self.subinplace(add_model_subs)

    def subconstr_str(self, excluded=None):
        "The collapsed appearance of a ConstraintBase"
        if self.name:
            return "%s_%s" % (self.name, self.num)

    def subconstr_latex(self, excluded=None):
        "The collapsed appearance of a ConstraintBase"
        if self.name:
            return "%s_{%s}" % (self.name, self.num)
<|endoftext|>


<commit_before>import csv
import re
import uuid
import os.path
from StringIO import StringIO

from django.http import Http404
from django.shortcuts import render, redirect
from django.core.urlresolvers import reverse
from django.core.paginator import Paginator
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
from django.conf import settings
from django.utils.datastructures import SortedDict

from vumi.utils import normalize_msisdn

from go.vumitools.contact import Contact
from go.contacts.forms import (
    ContactForm, NewContactGroupForm, UploadContactsForm,
    SelectContactGroupForm)

def _query_to_kwargs(query):
    pattern = r'(?P<key>[^ :]+):[ ]*(?P<value>[^:]*[^ :])(?:(?=( [^:]+:)|$))'
    tuples = re.findall(pattern, query)
    return dict([(t[0], t[1]) for t in tuples])

def _filter_contacts(contacts, request_params):
    query = request_params.get('q', None)
    selected_letter = None

    if query:
        ql = query.lower()
        contacts = [c for c in contacts
                    if (ql in c.name.lower()) or (ql in c.surname.lower())]
    else:
        selected_letter = request_params.get('l', 'a').lower()
        contacts = [c for c in contacts
                    if c.surname.lower().startswith(selected_letter)]

    return {
        'query': query,
        'selected_letter': selected_letter,
        'selected_contacts': sorted(contacts,
                                    key=lambda c: c.name.lower()[0]),
        }

def _read_data_from_csv_file(csvfile, field_names):
    dialect = csv.Sniffer().sniff(csvfile.read(1024))
    csvfile.seek(0)
    reader = csv.DictReader(csvfile, field_names, dialect=dialect)
    for row in reader:
        # Only process rows that actually have data
        if any([column for column in row]):
            # Our Riak client requires unicode for all keys & values stored.
            unicoded_row = dict([(key, unicode(value, 'utf-8'))
                                    for key, value in row.items()])
            yield unicoded_row

def _group_url(group_key):
    return reverse('contacts:group', kwargs={'group_key': group_key})

@login_required
def index(request):
    return redirect(reverse('contacts:groups'))

@login_required
def groups(request):
    contact_store = request.user_api.contact_store
    if request.POST:
        new_contact_group_form = NewContactGroupForm(request.POST)
        if new_contact_group_form.is_valid():
            group = contact_store.new_group(
                new_contact_group_form.cleaned_data['name'])
            messages.add_message(request, messages.INFO, 'New group created')
            return redirect(_group_url(group.key))
    else:
        new_contact_group_form = NewContactGroupForm()

    query = request.GET.get('q', None)
    if query:
        query_kwargs = _query_to_kwargs(query)
        if query_kwargs:
            groups = contact_store.groups.search(**query_kwargs)
        else:
            groups = []
    else:
        groups = contact_store.list_groups()

    paginator = Paginator(groups, 5)
    page = paginator.page(request.GET.get('p', 1))
    return render(request, 'contacts/groups.html', {
        'paginator': paginator,
        'page': page,
        'query': query,
        'new_contact_group_form': new_contact_group_form,
        'country_code': settings.VUMI_COUNTRY_CODE,
    })

def _is_header_row(columns):
    column_set = set([column.lower() for column in columns])
    hint_set = set(['phone', 'contact', 'msisdn', 'number'])
    return hint_set.intersection(column_set)

def _guess_headers_and_row(csv_data):
    [first_row, second_row] = csv.reader(StringIO(csv_data))
    default_headers = {
        'name': 'Name',
        'surname': 'Surname',
        'bbm_pin': 'BBM Pin',
        'msisdn': 'Contact Number',
        'gtalk_id': 'GTalk (or XMPP) address',
        'dob': 'Date of Birth',
        'facebook_id': 'Facebook ID',
        'twitter_handle': 'Twitter handle',
        'email_address': 'Email address',
    }

    if _is_header_row(first_row):
        sample_row = SortedDict(zip(second_row, first_row))
        for column in first_row:
            default_headers.setdefault(column, column)
        return True, default_headers, sample_row
    return (False, default_headers,
        SortedDict([(column, None) for column in first_row]))

def _get_file_hints(content_file):
    # Save the file object temporarily so we can present
    # some UI to help the user figure out which columns are
    # what of what type.
    temp_file_name = uuid.uuid4().hex
    temp_file_path = default_storage.save(os.path.join('tmp',
        '%s.csv' % (temp_file_name,)), content_file)
    # Store the first two lines in the session, we'll present these
    # in the UI on the following page to help the user determine
    # which column represents what.
    content_file.seek(0)
    first_two_lines = '\n'.join([
        content_file.readline().strip() for i in range(2)])

    return temp_file_path, first_two_lines

def _store_file_hints_in_session(request, path, data):
    # Not too happy with this method but I don't want to
    # be writing the same session keys everywhere.
    request.session['uploaded_csv_path'] = path
    request.session['uploaded_csv_data'] = data
    return request

def _get_file_hints_from_session(request):
    return [
        request.session['uploaded_csv_path'],
        request.session['uploaded_csv_data'],
    ]

def _clear_file_hints_from_session(request):
    del request.session['uploaded_csv_data']
    del request.session['uploaded_csv_path']

def _has_uncompleted_csv_import(request):
    return (('uploaded_csv_data' in request.session)
        and ('uploaded_csv_path' in request.session))

def _import_csv_file(group, csv_path, field_names, has_header):
    full_path = os.path.join(settings.MEDIA_ROOT, csv_path)
    # open in Universal mode to allow us to reed files with Windows,
    # MacOS9 & Unix line-endings
    csv_file = open(full_path, 'rU')
    data_dictionaries = _read_data_from_csv_file(csv_file,
                                field_names)

    # We need to know what we cannot set to avoid a
    # CSV import overwriting things like account details.
    excluded_attributes = ['user_account',
                            'created_at',
                            'extra']

    known_attributes = [attribute
        for attribute in Contact.field_descriptors.keys()
        if attribute not in excluded_attributes]

    # It's a generator so loop over it and save as contacts
    # in the contact_store, normalizing anything we need to
    for counter, data_dictionary in enumerate(data_dictionaries):

        # If we've determined that the first line of the file is
        # a header then skip it.
        if has_header and counter == 0:
            continue

        # Make sure we set this group they're being uploaded in to
        groups = data_dictionary.setdefault('groups', [])
        groups.append(group.key)

        # Make sure we normalize the msisdn before saving in the db
        if 'msisdn' in data_dictionary:
            msisdn = data_dictionary['msisdn']
            # TODO: fix normalization, Vumi Go won't be bound to a single
            #       country code which can be used for normalization.
            normalized_msisdn = normalize_msisdn(msisdn,
                country_code=settings.VUMI_COUNTRY_CODE)
            data_dictionary.update({
                'msisdn': unicode(normalized_msisdn, 'utf-8')
            })

        # Populate this with whatever we'll be sending to the
        # contact to be saved
        contact_dictionary = {}
        dynamic_fields = {}
        for key, value in data_dictionary.items():
            if key in known_attributes:
                contact_dictionary[key] = value
            else:
                dynamic_fields[key] = value

        yield ((counter if has_header else counter + 1),
                contact_dictionary, dynamic_fields)

@login_required
def group(request, group_key):
    contact_store = request.user_api.contact_store
    group = contact_store.get_group(group_key)
    if group is None:
        raise Http404

    if request.method == 'POST':
        if '_save_group' in request.POST:
            group_form = NewContactGroupForm(request.POST)
            if group_form.is_valid():
                group.name = group_form.cleaned_data['name']
                group.save()
            messages.info(request, 'The group name has been updated')
            return redirect(_group_url(group.key))
        elif '_complete_csv_upload' in request.POST:
            try:
                csv_path, csv_data = _get_file_hints_from_session(request)
                has_header, _, sample_row = _guess_headers_and_row(csv_data)

                # Grab the selected field names from the submitted form
                # by looping over the expect n number of `column-n` keys being
                # posted
                field_names = [request.POST.get('column-%s' % i) for i in
                                range(len(sample_row))]

                for csv_data in _import_csv_file(group, csv_path, field_names,
                                                    has_header):
                    [count, contact_dictionary, dynamic_fields] = csv_data
                    contact = contact_store.new_contact(**contact_dictionary)
                    contact.extra.update(dynamic_fields)
                    contact.save()

                messages.info(request,
                    'Success! %s contacts imported.' % (
                        count,))

                _clear_file_hints_from_session(request)
                return redirect(_group_url(group.key))

            except ValueError:
                messages.error(request, 'Something is wrong with the file')

        else:
            upload_contacts_form = UploadContactsForm(request.POST,
                                                        request.FILES)
            if upload_contacts_form.is_valid():
                file_object = upload_contacts_form.cleaned_data['file']
                _store_file_hints_in_session(request,
                    *_get_file_hints(file_object))
                return redirect(_group_url(group.key))

    context = {
        'group': group,
        'country_code': settings.VUMI_COUNTRY_CODE,
    }

    if 'clear-upload' in request.GET:
        # FIXME this is a debug statement
        del request.session['uploaded_csv_data']

    if _has_uncompleted_csv_import(request):
        try:
            csv_path, csv_data = _get_file_hints_from_session(request)
            has_header, headers, row = _guess_headers_and_row(csv_data)
            context.update({
                'csv_data_headers': headers,
                'csv_data_row': row,
            })
        except ValueError:
            messages.error(request, 'Something is wrong with the file')

    if ':' in request.GET.get('q', ''):
        query = request.GET['q']
        query_kwargs = _query_to_kwargs(query)
        context.update({
            'query': query,
            'selected_contacts': [contact for contact in
                            contact_store.contacts.search(**query_kwargs)],
        })
    else:
        context.update(_filter_contacts(group.backlinks.contacts(),
                                            request.GET))
    return render(request, 'contacts/group.html', context)

@login_required
def people(request):
    contact_store = request.user_api.contact_store
    if request.method == 'POST':
        # first parse the CSV file and create Contact instances
        # from them for attaching to a group later
        upload_contacts_form = UploadContactsForm(request.POST, request.FILES)
        if upload_contacts_form.is_valid():
            # We could be creating a new contact group.
            if request.POST.get('name'):
                new_group_form = NewContactGroupForm(request.POST)
                if new_group_form.is_valid():
                    group = contact_store.new_group(
                        new_group_form.cleaned_data['name'])

            # We could be using an existing contact group.
            if request.POST.get('contact_group'):
                select_group_form = SelectContactGroupForm(
                    request.POST, groups=contact_store.list_groups())
                if select_group_form.is_valid():
                    group = contact_store.get_group(
                        select_group_form.cleaned_data['contact_group'])

            if group is None:
                messages.error(request, 'Please select a group or provide '
                    'a new group name.')
            else:
                file_object = upload_contacts_form.cleaned_data['file']
                _store_file_hints_in_session(request,
                    *_get_file_hints(file_object))
                return redirect(_group_url(group.key))
        else:
            messages.error(request, 'Something went wrong with the upload.')
    else:
        upload_contacts_form = UploadContactsForm()

    select_contact_group_form = SelectContactGroupForm(
        groups=contact_store.list_groups())
    contacts = contact_store.list_contacts()
    context = {
        'upload_contacts_form': upload_contacts_form,
        'contacts': contacts,
        'country_code': settings.VUMI_COUNTRY_CODE,
        'select_contact_group_form': select_contact_group_form,
        }

    if ':' in request.GET.get('q', ''):
        query = request.GET['q']
        query_kwargs = _query_to_kwargs(query)
        context.update({
            'query': query,
            'selected_contacts': [contact for contact in
                            contact_store.contacts.search(**query_kwargs)],
        })
    else:
        context.update(_filter_contacts(contacts, request.GET))
    return render(request, 'contacts/people.html', context)

@login_required
def person(request, person_key):
    contact_store = request.user_api.contact_store
    contact = contact_store.get_contact_by_key(person_key)
    if contact is None:
        raise Http404
    groups = contact_store.list_groups()
    if request.method == 'POST':
        form = ContactForm(request.POST, groups=groups)
        if form.is_valid():
            for k, v in form.cleaned_data.items():
                if k == 'groups':
                    contact.groups.clear()
                    for group in v:
                        contact.add_to_group(group)
                    continue
                setattr(contact, k, v)
            contact.save()
            messages.add_message(request, messages.INFO, 'Profile Updated')
            return redirect(reverse('contacts:person', kwargs={
                'person_key': contact.key}))
        else:
            messages.add_message(request, messages.ERROR,
                'Please correct the problem below.')
    else:
        form = ContactForm(groups=groups, initial={
            'name': contact.name,
            'surname': contact.surname,
            'email_address': contact.email_address,
            'msisdn': contact.msisdn,
            'twitter_handle': contact.twitter_handle,
            'facebook_id': contact.facebook_id,
            'bbm_pin': contact.bbm_pin,
            'gtalk_id': contact.gtalk_id,
            'dob': contact.dob,
            'groups': [group.key for group in contact.groups.get_all()],
        })

    return render(request, 'contacts/person.html', {
        'contact': contact,
        'contact_extra_items': contact.extra.items(),
        'form': form,
        'country_code': settings.VUMI_COUNTRY_CODE,
    })

@login_required
def new_person(request):
    contact_store = request.user_api.contact_store
    groups = contact_store.list_groups()
    if request.POST:
        form = ContactForm(request.POST, groups=groups)
        if form.is_valid():
            contact = contact_store.new_contact(**form.cleaned_data)
            messages.add_message(request, messages.INFO, 'Profile Created')
            return redirect(reverse('contacts:person', kwargs={
                'person_key': contact.key}))
        else:
            messages.add_message(request, messages.ERROR,
                'Please correct the problem below.')
    else:
        form = ContactForm(groups=groups)
    return render(request, 'contacts/new_person.html', {
        'form': form,
        'country_code': settings.VUMI_COUNTRY_CODE,
    })
<commit_msg>Dynamic fields can now be initialized, removes double save() call<commit_after>import csv
import re
import uuid
import os.path
from StringIO import StringIO

from django.http import Http404
from django.shortcuts import render, redirect
from django.core.urlresolvers import reverse
from django.core.paginator import Paginator
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
from django.conf import settings
from django.utils.datastructures import SortedDict

from vumi.utils import normalize_msisdn

from go.vumitools.contact import Contact
from go.contacts.forms import (
    ContactForm, NewContactGroupForm, UploadContactsForm,
    SelectContactGroupForm)

def _query_to_kwargs(query):
    pattern = r'(?P<key>[^ :]+):[ ]*(?P<value>[^:]*[^ :])(?:(?=( [^:]+:)|$))'
    tuples = re.findall(pattern, query)
    return dict([(t[0], t[1]) for t in tuples])

def _filter_contacts(contacts, request_params):
    query = request_params.get('q', None)
    selected_letter = None

    if query:
        ql = query.lower()
        contacts = [c for c in contacts
                    if (ql in c.name.lower()) or (ql in c.surname.lower())]
    else:
        selected_letter = request_params.get('l', 'a').lower()
        contacts = [c for c in contacts
                    if c.surname.lower().startswith(selected_letter)]

    return {
        'query': query,
        'selected_letter': selected_letter,
        'selected_contacts': sorted(contacts,
                                    key=lambda c: c.name.lower()[0]),
        }

def _read_data_from_csv_file(csvfile, field_names):
    dialect = csv.Sniffer().sniff(csvfile.read(1024))
    csvfile.seek(0)
    reader = csv.DictReader(csvfile, field_names, dialect=dialect)
    for row in reader:
        # Only process rows that actually have data
        if any([column for column in row]):
            # Our Riak client requires unicode for all keys & values stored.
            unicoded_row = dict([(key, unicode(value, 'utf-8'))
                                    for key, value in row.items()])
            yield unicoded_row

def _group_url(group_key):
    return reverse('contacts:group', kwargs={'group_key': group_key})

@login_required
def index(request):
    return redirect(reverse('contacts:groups'))

@login_required
def groups(request):
    contact_store = request.user_api.contact_store
    if request.POST:
        new_contact_group_form = NewContactGroupForm(request.POST)
        if new_contact_group_form.is_valid():
            group = contact_store.new_group(
                new_contact_group_form.cleaned_data['name'])
            messages.add_message(request, messages.INFO, 'New group created')
            return redirect(_group_url(group.key))
    else:
        new_contact_group_form = NewContactGroupForm()

    query = request.GET.get('q', None)
    if query:
        query_kwargs = _query_to_kwargs(query)
        if query_kwargs:
            groups = contact_store.groups.search(**query_kwargs)
        else:
            groups = []
    else:
        groups = contact_store.list_groups()

    paginator = Paginator(groups, 5)
    page = paginator.page(request.GET.get('p', 1))
    return render(request, 'contacts/groups.html', {
        'paginator': paginator,
        'page': page,
        'query': query,
        'new_contact_group_form': new_contact_group_form,
        'country_code': settings.VUMI_COUNTRY_CODE,
    })

def _is_header_row(columns):
    column_set = set([column.lower() for column in columns])
    hint_set = set(['phone', 'contact', 'msisdn', 'number'])
    return hint_set.intersection(column_set)

def _guess_headers_and_row(csv_data):
    [first_row, second_row] = csv.reader(StringIO(csv_data))
    default_headers = {
        'name': 'Name',
        'surname': 'Surname',
        'bbm_pin': 'BBM Pin',
        'msisdn': 'Contact Number',
        'gtalk_id': 'GTalk (or XMPP) address',
        'dob': 'Date of Birth',
        'facebook_id': 'Facebook ID',
        'twitter_handle': 'Twitter handle',
        'email_address': 'Email address',
    }

    if _is_header_row(first_row):
        sample_row = SortedDict(zip(second_row, first_row))
        for column in first_row:
            default_headers.setdefault(column, column)
        return True, default_headers, sample_row
    return (False, default_headers,
        SortedDict([(column, None) for column in first_row]))

def _get_file_hints(content_file):
    # Save the file object temporarily so we can present
    # some UI to help the user figure out which columns are
    # what of what type.
    temp_file_name = uuid.uuid4().hex
    temp_file_path = default_storage.save(os.path.join('tmp',
        '%s.csv' % (temp_file_name,)), content_file)
    # Store the first two lines in the session, we'll present these
    # in the UI on the following page to help the user determine
    # which column represents what.
    content_file.seek(0)
    first_two_lines = '\n'.join([
        content_file.readline().strip() for i in range(2)])

    return temp_file_path, first_two_lines

def _store_file_hints_in_session(request, path, data):
    # Not too happy with this method but I don't want to
    # be writing the same session keys everywhere.
    request.session['uploaded_csv_path'] = path
    request.session['uploaded_csv_data'] = data
    return request

def _get_file_hints_from_session(request):
    return [
        request.session['uploaded_csv_path'],
        request.session['uploaded_csv_data'],
    ]

def _clear_file_hints_from_session(request):
    del request.session['uploaded_csv_data']
    del request.session['uploaded_csv_path']

def _has_uncompleted_csv_import(request):
    return (('uploaded_csv_data' in request.session)
        and ('uploaded_csv_path' in request.session))

def _import_csv_file(group, csv_path, field_names, has_header):
    full_path = os.path.join(settings.MEDIA_ROOT, csv_path)
    # open in Universal mode to allow us to reed files with Windows,
    # MacOS9 & Unix line-endings
    csv_file = open(full_path, 'rU')
    data_dictionaries = _read_data_from_csv_file(csv_file,
                                field_names)

    # We need to know what we cannot set to avoid a
    # CSV import overwriting things like account details.
    excluded_attributes = ['user_account',
                            'created_at',
                            'extra']

    known_attributes = [attribute
        for attribute in Contact.field_descriptors.keys()
        if attribute not in excluded_attributes]

    # It's a generator so loop over it and save as contacts
    # in the contact_store, normalizing anything we need to
    for counter, data_dictionary in enumerate(data_dictionaries):

        # If we've determined that the first line of the file is
        # a header then skip it.
        if has_header and counter == 0:
            continue

        # Make sure we set this group they're being uploaded in to
        groups = data_dictionary.setdefault('groups', [])
        groups.append(group.key)

        # Make sure we normalize the msisdn before saving in the db
        if 'msisdn' in data_dictionary:
            msisdn = data_dictionary['msisdn']
            # TODO: fix normalization, Vumi Go won't be bound to a single
            #       country code which can be used for normalization.
            normalized_msisdn = normalize_msisdn(msisdn,
                country_code=settings.VUMI_COUNTRY_CODE)
            data_dictionary.update({
                'msisdn': unicode(normalized_msisdn, 'utf-8')
            })

        # Populate this with whatever we'll be sending to the
        # contact to be saved
        contact_dictionary = {}
        for key, value in data_dictionary.items():
            if key in known_attributes:
                contact_dictionary[key] = value
            else:
                extra = contact_dictionary.setdefault('extra', {})
                extra[key] = value

        yield (counter if has_header else counter + 1, contact_dictionary)

@login_required
def group(request, group_key):
    contact_store = request.user_api.contact_store
    group = contact_store.get_group(group_key)
    if group is None:
        raise Http404

    if request.method == 'POST':
        if '_save_group' in request.POST:
            group_form = NewContactGroupForm(request.POST)
            if group_form.is_valid():
                group.name = group_form.cleaned_data['name']
                group.save()
            messages.info(request, 'The group name has been updated')
            return redirect(_group_url(group.key))
        elif '_complete_csv_upload' in request.POST:
            try:
                csv_path, csv_data = _get_file_hints_from_session(request)
                has_header, _, sample_row = _guess_headers_and_row(csv_data)

                # Grab the selected field names from the submitted form
                # by looping over the expect n number of `column-n` keys being
                # posted
                field_names = [request.POST.get('column-%s' % i) for i in
                                range(len(sample_row))]

                for csv_data in _import_csv_file(group, csv_path, field_names,
                                                    has_header):
                    [count, contact_dictionary] = csv_data
                    contact_store.new_contact(**contact_dictionary)

                messages.info(request,
                    'Success! %s contacts imported.' % (
                        count,))

                _clear_file_hints_from_session(request)
                return redirect(_group_url(group.key))

            except ValueError:
                messages.error(request, 'Something is wrong with the file')

        else:
            upload_contacts_form = UploadContactsForm(request.POST,
                                                        request.FILES)
            if upload_contacts_form.is_valid():
                file_object = upload_contacts_form.cleaned_data['file']
                _store_file_hints_in_session(request,
                    *_get_file_hints(file_object))
                return redirect(_group_url(group.key))

    context = {
        'group': group,
        'country_code': settings.VUMI_COUNTRY_CODE,
    }

    if 'clear-upload' in request.GET:
        # FIXME this is a debug statement
        del request.session['uploaded_csv_data']

    if _has_uncompleted_csv_import(request):
        try:
            csv_path, csv_data = _get_file_hints_from_session(request)
            has_header, headers, row = _guess_headers_and_row(csv_data)
            context.update({
                'csv_data_headers': headers,
                'csv_data_row': row,
            })
        except ValueError:
            messages.error(request, 'Something is wrong with the file')

    if ':' in request.GET.get('q', ''):
        query = request.GET['q']
        query_kwargs = _query_to_kwargs(query)
        context.update({
            'query': query,
            'selected_contacts': [contact for contact in
                            contact_store.contacts.search(**query_kwargs)],
        })
    else:
        context.update(_filter_contacts(group.backlinks.contacts(),
                                            request.GET))
    return render(request, 'contacts/group.html', context)

@login_required
def people(request):
    contact_store = request.user_api.contact_store
    if request.method == 'POST':
        # first parse the CSV file and create Contact instances
        # from them for attaching to a group later
        upload_contacts_form = UploadContactsForm(request.POST, request.FILES)
        if upload_contacts_form.is_valid():
            # We could be creating a new contact group.
            if request.POST.get('name'):
                new_group_form = NewContactGroupForm(request.POST)
                if new_group_form.is_valid():
                    group = contact_store.new_group(
                        new_group_form.cleaned_data['name'])

            # We could be using an existing contact group.
            if request.POST.get('contact_group'):
                select_group_form = SelectContactGroupForm(
                    request.POST, groups=contact_store.list_groups())
                if select_group_form.is_valid():
                    group = contact_store.get_group(
                        select_group_form.cleaned_data['contact_group'])

            if group is None:
                messages.error(request, 'Please select a group or provide '
                    'a new group name.')
            else:
                file_object = upload_contacts_form.cleaned_data['file']
                _store_file_hints_in_session(request,
                    *_get_file_hints(file_object))
                return redirect(_group_url(group.key))
        else:
            messages.error(request, 'Something went wrong with the upload.')
    else:
        upload_contacts_form = UploadContactsForm()

    select_contact_group_form = SelectContactGroupForm(
        groups=contact_store.list_groups())
    contacts = contact_store.list_contacts()
    context = {
        'upload_contacts_form': upload_contacts_form,
        'contacts': contacts,
        'country_code': settings.VUMI_COUNTRY_CODE,
        'select_contact_group_form': select_contact_group_form,
        }

    if ':' in request.GET.get('q', ''):
        query = request.GET['q']
        query_kwargs = _query_to_kwargs(query)
        context.update({
            'query': query,
            'selected_contacts': [contact for contact in
                            contact_store.contacts.search(**query_kwargs)],
        })
    else:
        context.update(_filter_contacts(contacts, request.GET))
    return render(request, 'contacts/people.html', context)

@login_required
def person(request, person_key):
    contact_store = request.user_api.contact_store
    contact = contact_store.get_contact_by_key(person_key)
    if contact is None:
        raise Http404
    groups = contact_store.list_groups()
    if request.method == 'POST':
        form = ContactForm(request.POST, groups=groups)
        if form.is_valid():
            for k, v in form.cleaned_data.items():
                if k == 'groups':
                    contact.groups.clear()
                    for group in v:
                        contact.add_to_group(group)
                    continue
                setattr(contact, k, v)
            contact.save()
            messages.add_message(request, messages.INFO, 'Profile Updated')
            return redirect(reverse('contacts:person', kwargs={
                'person_key': contact.key}))
        else:
            messages.add_message(request, messages.ERROR,
                'Please correct the problem below.')
    else:
        form = ContactForm(groups=groups, initial={
            'name': contact.name,
            'surname': contact.surname,
            'email_address': contact.email_address,
            'msisdn': contact.msisdn,
            'twitter_handle': contact.twitter_handle,
            'facebook_id': contact.facebook_id,
            'bbm_pin': contact.bbm_pin,
            'gtalk_id': contact.gtalk_id,
            'dob': contact.dob,
            'groups': [group.key for group in contact.groups.get_all()],
        })

    return render(request, 'contacts/person.html', {
        'contact': contact,
        'contact_extra_items': contact.extra.items(),
        'form': form,
        'country_code': settings.VUMI_COUNTRY_CODE,
    })

@login_required
def new_person(request):
    contact_store = request.user_api.contact_store
    groups = contact_store.list_groups()
    if request.POST:
        form = ContactForm(request.POST, groups=groups)
        if form.is_valid():
            contact = contact_store.new_contact(**form.cleaned_data)
            messages.add_message(request, messages.INFO, 'Profile Created')
            return redirect(reverse('contacts:person', kwargs={
                'person_key': contact.key}))
        else:
            messages.add_message(request, messages.ERROR,
                'Please correct the problem below.')
    else:
        form = ContactForm(groups=groups)
    return render(request, 'contacts/new_person.html', {
        'form': form,
        'country_code': settings.VUMI_COUNTRY_CODE,
    })
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright (c) 2015-2018, Exa Analytics Development Team
# Distributed under the terms of the Apache License 2.0
#import os
import numpy as np
import pandas as pd
from unittest import TestCase

from exatomic import gaussian
from exatomic.base import resource
from exatomic.gaussian import Output, Fchk

class TestFchk(TestCase):
    def setUp(self):
        self.mam1 = Fchk(resource('g09-ch3nh2-631g.fchk'))
        self.mam2 = Fchk(resource('g09-ch3nh2-augccpvdz.fchk'))
        self.mam3 = Fchk(resource('g16-methyloxirane-def2tzvp-freq.fchk'))
        self.mam4 = Fchk(resource('g16-h2o2-def2tzvp-freq.fchk'))
        self.mam5 = Fchk(resource('g16-naproxen-def2tzvp-freq.fchk'))

    def test_parse_atom(self):
        self.mam1.parse_atom()
        self.assertEqual(self.mam1.atom.shape[0], 7)
        self.assertTrue(np.all(pd.notnull(self.mam1.atom)))
        self.mam2.parse_atom()
        self.assertEqual(self.mam2.atom.shape[0], 7)
        self.assertTrue(np.all(pd.notnull(self.mam2.atom)))

    def test_parse_basis_set(self):
        self.mam1.parse_basis_set()
        self.assertEqual(self.mam1.basis_set.shape[0], 32)
        self.assertTrue(np.all(pd.notnull(self.mam1.basis_set)))
        self.mam2.parse_basis_set()
        self.assertEqual(self.mam2.basis_set.shape[0], 53)
        self.assertTrue(np.all(pd.notnull(self.mam2.basis_set)))

    def test_parse_orbital(self):
        self.mam1.parse_orbital()
        self.assertEqual(self.mam1.orbital.shape[0], 28)
        self.assertTrue(np.all(pd.notnull(self.mam1.orbital)))
        self.mam2.parse_orbital()
        self.assertEqual(self.mam2.orbital.shape[0], 91)
        self.assertTrue(np.all(pd.notnull(self.mam2.orbital)))

    def test_parse_momatrix(self):
        self.mam1.parse_momatrix()
        self.assertEqual(self.mam1.momatrix.shape[0], 784)
        self.assertTrue(np.all(pd.notnull(self.mam1.momatrix)))
        self.mam2.parse_momatrix()
        self.assertEqual(self.mam2.momatrix.shape[0], 8281)
        self.assertTrue(np.all(pd.notnull(self.mam2.momatrix)))

    def test_parse_basis_set_order(self):
        self.mam1.parse_basis_set_order()
        self.assertEqual(self.mam1.basis_set_order.shape[0], 28)
        self.assertTrue(np.all(pd.notnull(self.mam1.basis_set_order)))
        self.mam2.parse_basis_set_order()
        self.assertEqual(self.mam2.basis_set_order.shape[0], 91)
        self.assertTrue(np.all(pd.notnull(self.mam2.basis_set_order)))

    def test_parse_frame(self):
        self.mam1.parse_frame()
        self.assertEqual(self.mam1.frame.shape[0], 1)
        self.assertTrue(np.all(pd.notnull(self.mam1.frame)))
        self.mam2.parse_frame()
        self.assertEqual(self.mam2.frame.shape[0], 1)
        self.assertTrue(np.all(pd.notnull(self.mam2.frame)))

    def test_parse_frequency(self):
        self.mam3.parse_frequency()
        self.assertEqual(self.mam3.frequency.shape[0], 240)
        self.assertTrue(np.all(pd.notnull(self.mam3.frequency)))
        self.mam4.parse_frequency()
        self.assertEqual(self.mam4.frequency.shape[0], 24)
        self.assertTrue(np.all(pd.notnull(self.mam4.frequency)))
        self.mam5.parse_frequency()
        self.assertEqual(self.mam5.frequency.shape[0], 2697)
        self.assertTrue(np.all(pd.notnull(self.mam5.frequency)))

    def test_parse_frequency_ext(self):
        self.mam3.parse_frequency_ext()
        self.assertEqual(self.mam3.frequency_ext.shape[0], 24)
        self.assertTrue(np.all(pd.notnull(self.mam3.frequency_ext)))
        self.mam4.parse_frequency_ext()
        self.assertEqual(self.mam4.frequency_ext.shape[0], 6)
        self.assertTrue(np.all(pd.notnull(self.mam4.frequency_ext)))
        self.mam5.parse_frequency_ext()
        self.assertEqual(self.mam5.frequency_ext.shape[0], 87)
        self.assertTrue(np.all(pd.notnull(self.mam5.frequency_ext)))

    def test_parse_gradient(self):
        self.mam3.parse_gradient()
        self.assertEqual(self.mam3.gradient.shape[0], 10)
        self.assertTrue(np.all(pd.notnull(self.mam3.gradient)))
        self.mam4.parse_gradient()
        self.assertEqual(self.mam4.gradient.shape[0], 4)
        self.assertTrue(np.all(pd.notnull(self.mam4.gradient)))
        self.mam5.parse_gradient()
        self.assertEqual(self.mam5.gradient.shape[0], 31)
        self.assertTrue(np.all(pd.notnull(self.mam5.gradient)))

    def test_shielding_tensor(self):
        # TODO: add this thing
        pass

    def test_to_universe(self):
        """Test the to_universe method."""
        mam1 = self.mam1.to_universe(ignore=True)
        mam2 = self.mam2.to_universe(ignore=True)
        for uni in [mam1, mam2]:
            # cannot add frequency and frequency_ext attributes as they require
            # very specific inputs
            for attr in ['atom', 'basis_set', 'basis_set_order',
                         'momatrix', 'orbital', 'frame']:
                self.assertTrue(hasattr(uni, attr))

class TestOutput(TestCase):
    """
    This test ensures that the parsing functionality works on
    a smattering of output files that were generated with the
    Gaussian software package. Target syntax is for Gaussian
    09.
    """
    def setUp(self):
        # TODO : add some cartesian basis set files
        #        a geometry optimization and
        #        maybe properties? like the frequency
        #        and tddft calcs
        self.uo2 = Output(resource('g09-uo2.out'))
        self.mam3 = Output(resource('g09-ch3nh2-631g.out'))
        self.mam4 = Output(resource('g09-ch3nh2-augccpvdz.out'))
        # need two because of the current limitations in the parse_frequency code
        self.meth_opt = Output(resource('g16-methyloxirane-def2tzvp-opt.out'))
        self.meth_freq = Output(resource('g16-methyloxirane-def2tzvp-freq.out'))
        self.nap_tddft = Output(resource('g16-naproxen-def2tzvp-tddft.out'))
        self.h2o2_tddft = Output(resource('g16-h2o2-def2tzvp-tddft.out'))
        self.nap_opt = Output(resource('g16-naproxen-def2tzvp-opt.out'))
        self.nap_freq = Output(resource('g16-naproxen-def2tzvp-freq.out'))

    def test_parse_atom(self):
        self.uo2.parse_atom()
        self.assertEqual(self.uo2.atom.shape[0], 3)
        self.assertTrue(np.all(pd.notnull(self.uo2.atom)))
        self.mam3.parse_atom()
        self.assertEqual(self.mam3.atom.shape[0], 7)
        self.assertTrue(np.all(pd.notnull(self.mam3.atom)))
        self.mam4.parse_atom()
        self.assertEqual(self.mam4.atom.shape[0], 7)
        self.assertTrue(np.all(pd.notnull(self.mam4.atom)))
        self.meth_opt.parse_atom()
        self.assertEqual(self.meth_opt.atom.shape[0], 120)
        self.assertTrue(np.all(pd.notnull(self.meth_opt.atom)))
        self.nap_opt.parse_atom()
        self.assertEqual(self.nap_opt.atom.shape[0], 806)
        self.assertTrue(np.all(pd.notnull(self.nap_opt.atom)))

    def test_parse_basis_set(self):
        self.uo2.parse_basis_set()
        self.assertEqual(self.uo2.basis_set.shape[0], 49)
        self.assertTrue(np.all(pd.notnull(self.uo2.basis_set)))
        self.mam3.parse_basis_set()
        self.assertEqual(self.mam3.basis_set.shape[0], 32)
        self.assertTrue(np.all(pd.notnull(self.mam3.basis_set)))
        self.mam4.parse_basis_set()
        self.assertEqual(self.mam4.basis_set.shape[0], 53)
        self.assertTrue(np.all(pd.notnull(self.mam4.basis_set)))

    def test_parse_orbital(self):
        self.uo2.parse_orbital()
        self.assertEqual(self.uo2.orbital.shape[0], 141)
        self.assertTrue(np.all(pd.notnull(self.uo2.orbital)))
        self.mam3.parse_orbital()
        self.assertEqual(self.mam3.orbital.shape[0], 28)
        self.assertTrue(np.all(pd.notnull(self.mam3.orbital)))
        self.mam4.parse_orbital()
        self.assertEqual(self.mam4.orbital.shape[0], 91)
        self.assertTrue(np.all(pd.notnull(self.mam4.orbital)))
        self.meth_opt.parse_orbital()
        self.assertEqual(self.meth_opt.orbital.shape[0], 160)
        self.assertTrue(np.all(pd.notnull(self.meth_opt.orbital)))
        self.nap_tddft.parse_orbital()
        self.assertEqual(self.nap_tddft.orbital.shape[0], 611)
        self.assertTrue(np.all(pd.notnull(self.nap_tddft.orbital)))

    def test_parse_momatrix(self):
        self.uo2.parse_momatrix()
        self.assertEqual(self.uo2.momatrix.shape[0], 19881)
        self.assertTrue(np.all(pd.notnull(self.uo2.momatrix)))
        self.mam3.parse_momatrix()
        self.assertEqual(self.mam3.momatrix.shape[0], 784)
        self.assertTrue(np.all(pd.notnull(self.mam3.momatrix)))
        self.mam4.parse_momatrix()
        self.assertEqual(self.mam4.momatrix.shape[0], 8281)
        self.assertTrue(np.all(pd.notnull(self.mam4.momatrix)))

    def test_parse_basis_set_order(self):
        self.uo2.parse_basis_set_order()
        self.assertEqual(self.uo2.basis_set_order.shape[0], 141)
        self.assertTrue(np.all(pd.notnull(self.uo2.basis_set_order)))
        self.mam3.parse_basis_set_order()
        self.assertEqual(self.mam3.basis_set_order.shape[0], 28)
        self.assertTrue(np.all(pd.notnull(self.mam3.basis_set_order)))
        self.mam4.parse_basis_set_order()
        self.assertEqual(self.mam4.basis_set_order.shape[0], 91)
        self.assertTrue(np.all(pd.notnull(self.mam4.basis_set_order)))

    def test_parse_frame(self):
        self.uo2.parse_frame()
        self.assertEqual(self.uo2.frame.shape[0], 1)
        self.assertTrue(np.all(pd.notnull(self.uo2.frame)))
        self.mam3.parse_frame()
        self.assertEqual(self.mam3.frame.shape[0], 1)
        self.assertTrue(np.all(pd.notnull(self.mam3.frame)))
        self.mam4.parse_frame()
        self.assertEqual(self.mam4.frame.shape[0], 1)
        self.assertTrue(np.all(pd.notnull(self.mam4.frame)))
        self.meth_opt.parse_frame()
        self.assertEqual(self.meth_opt.frame.shape[0], 12)
        self.assertTrue(np.all(pd.notnull(self.meth_opt.frame)))
        self.nap_opt.parse_frame()
        self.assertEqual(self.nap_opt.frame.shape[0], 26)
        self.assertTrue(np.all(pd.notnull(self.nap_opt.frame)))

    def test_parse_frequency(self):
        self.meth_freq.parse_frequency()
        self.assertEqual(self.meth_freq.frequency.shape[0], 240)
        self.assertTrue(np.all(pd.notnull(self.meth_freq.frequency)))
        self.nap_freq.parse_frequency()
        self.assertEqual(self.nap_freq.frequency.shape[0], 2697)
        self.assertTrue(np.all(pd.notnull(self.nap_freq.frequency)))

    def test_parse_excitation(self):
        self.nap_tddft.parse_excitation()
        self.assertEqual(self.nap_tddft.excitation.shape[0], 15)
        self.assertTrue(np.all(pd.notnull(self.nap_tddft.excitation)))
        self.h2o2_tddft.parse_excitation()
        self.assertEqual(self.h2o2_tddft.excitation.shape[0], 32)
        self.assertTrue(np.all(pd.notnull(self.h2o2_tddft.excitation)))

    def test_shielding_tensor(self):
        # TODO: add this thing
        pass

    def test_to_universe(self):
        """Test the to_universe method."""
        uo2 = self.uo2.to_universe(ignore=True)
        mam3 = self.mam3.to_universe(ignore=True)
        #meth_opt = self.meth_opt.to_universe(ignore=True)
        for uni in [uo2, mam3]:
            for attr in ['atom', 'basis_set', 'basis_set_order',
                         'momatrix', 'orbital', 'frame']:
                self.assertTrue(hasattr(uni, attr))
<commit_msg>Removing naproxen tests. Too much data.<commit_after># -*- coding: utf-8 -*-
# Copyright (c) 2015-2018, Exa Analytics Development Team
# Distributed under the terms of the Apache License 2.0
#import os
import numpy as np
import pandas as pd
from unittest import TestCase

from exatomic import gaussian
from exatomic.base import resource
from exatomic.gaussian import Output, Fchk

class TestFchk(TestCase):
    def setUp(self):
        self.mam1 = Fchk(resource('g09-ch3nh2-631g.fchk'))
        self.mam2 = Fchk(resource('g09-ch3nh2-augccpvdz.fchk'))
        self.mam3 = Fchk(resource('g16-methyloxirane-def2tzvp-freq.fchk'))
        self.mam4 = Fchk(resource('g16-h2o2-def2tzvp-freq.fchk'))

    def test_parse_atom(self):
        self.mam1.parse_atom()
        self.assertEqual(self.mam1.atom.shape[0], 7)
        self.assertTrue(np.all(pd.notnull(self.mam1.atom)))
        self.mam2.parse_atom()
        self.assertEqual(self.mam2.atom.shape[0], 7)
        self.assertTrue(np.all(pd.notnull(self.mam2.atom)))

    def test_parse_basis_set(self):
        self.mam1.parse_basis_set()
        self.assertEqual(self.mam1.basis_set.shape[0], 32)
        self.assertTrue(np.all(pd.notnull(self.mam1.basis_set)))
        self.mam2.parse_basis_set()
        self.assertEqual(self.mam2.basis_set.shape[0], 53)
        self.assertTrue(np.all(pd.notnull(self.mam2.basis_set)))

    def test_parse_orbital(self):
        self.mam1.parse_orbital()
        self.assertEqual(self.mam1.orbital.shape[0], 28)
        self.assertTrue(np.all(pd.notnull(self.mam1.orbital)))
        self.mam2.parse_orbital()
        self.assertEqual(self.mam2.orbital.shape[0], 91)
        self.assertTrue(np.all(pd.notnull(self.mam2.orbital)))

    def test_parse_momatrix(self):
        self.mam1.parse_momatrix()
        self.assertEqual(self.mam1.momatrix.shape[0], 784)
        self.assertTrue(np.all(pd.notnull(self.mam1.momatrix)))
        self.mam2.parse_momatrix()
        self.assertEqual(self.mam2.momatrix.shape[0], 8281)
        self.assertTrue(np.all(pd.notnull(self.mam2.momatrix)))

    def test_parse_basis_set_order(self):
        self.mam1.parse_basis_set_order()
        self.assertEqual(self.mam1.basis_set_order.shape[0], 28)
        self.assertTrue(np.all(pd.notnull(self.mam1.basis_set_order)))
        self.mam2.parse_basis_set_order()
        self.assertEqual(self.mam2.basis_set_order.shape[0], 91)
        self.assertTrue(np.all(pd.notnull(self.mam2.basis_set_order)))

    def test_parse_frame(self):
        self.mam1.parse_frame()
        self.assertEqual(self.mam1.frame.shape[0], 1)
        self.assertTrue(np.all(pd.notnull(self.mam1.frame)))
        self.mam2.parse_frame()
        self.assertEqual(self.mam2.frame.shape[0], 1)
        self.assertTrue(np.all(pd.notnull(self.mam2.frame)))

    def test_parse_frequency(self):
        self.mam3.parse_frequency()
        self.assertEqual(self.mam3.frequency.shape[0], 240)
        self.assertTrue(np.all(pd.notnull(self.mam3.frequency)))
        self.mam4.parse_frequency()
        self.assertEqual(self.mam4.frequency.shape[0], 24)
        self.assertTrue(np.all(pd.notnull(self.mam4.frequency)))

    def test_parse_frequency_ext(self):
        self.mam3.parse_frequency_ext()
        self.assertEqual(self.mam3.frequency_ext.shape[0], 24)
        self.assertTrue(np.all(pd.notnull(self.mam3.frequency_ext)))
        self.mam4.parse_frequency_ext()
        self.assertEqual(self.mam4.frequency_ext.shape[0], 6)
        self.assertTrue(np.all(pd.notnull(self.mam4.frequency_ext)))

    def test_parse_gradient(self):
        self.mam3.parse_gradient()
        self.assertEqual(self.mam3.gradient.shape[0], 10)
        self.assertTrue(np.all(pd.notnull(self.mam3.gradient)))
        self.mam4.parse_gradient()
        self.assertEqual(self.mam4.gradient.shape[0], 4)
        self.assertTrue(np.all(pd.notnull(self.mam4.gradient)))

    def test_shielding_tensor(self):
        # TODO: add this thing
        pass

    def test_to_universe(self):
        """Test the to_universe method."""
        mam1 = self.mam1.to_universe(ignore=True)
        mam2 = self.mam2.to_universe(ignore=True)
        for uni in [mam1, mam2]:
            # cannot add frequency and frequency_ext attributes as they require
            # very specific inputs
            for attr in ['atom', 'basis_set', 'basis_set_order',
                         'momatrix', 'orbital', 'frame']:
                self.assertTrue(hasattr(uni, attr))

class TestOutput(TestCase):
    """
    This test ensures that the parsing functionality works on
    a smattering of output files that were generated with the
    Gaussian software package. Target syntax is for Gaussian
    09.
    """
    def setUp(self):
        # TODO : add some cartesian basis set files
        #        a geometry optimization and
        #        maybe properties? like the frequency
        #        and tddft calcs
        self.uo2 = Output(resource('g09-uo2.out'))
        self.mam3 = Output(resource('g09-ch3nh2-631g.out'))
        self.mam4 = Output(resource('g09-ch3nh2-augccpvdz.out'))
        # need two because of the current limitations in the parse_frequency code
        self.meth_opt = Output(resource('g16-methyloxirane-def2tzvp-opt.out'))
        self.meth_freq = Output(resource('g16-methyloxirane-def2tzvp-freq.out'))
        self.nap_tddft = Output(resource('g16-naproxen-def2tzvp-tddft.out'))
        self.h2o2_tddft = Output(resource('g16-h2o2-def2tzvp-tddft.out'))
        self.nap_opt = Output(resource('g16-naproxen-def2tzvp-opt.out'))

    def test_parse_atom(self):
        self.uo2.parse_atom()
        self.assertEqual(self.uo2.atom.shape[0], 3)
        self.assertTrue(np.all(pd.notnull(self.uo2.atom)))
        self.mam3.parse_atom()
        self.assertEqual(self.mam3.atom.shape[0], 7)
        self.assertTrue(np.all(pd.notnull(self.mam3.atom)))
        self.mam4.parse_atom()
        self.assertEqual(self.mam4.atom.shape[0], 7)
        self.assertTrue(np.all(pd.notnull(self.mam4.atom)))
        self.meth_opt.parse_atom()
        self.assertEqual(self.meth_opt.atom.shape[0], 120)
        self.assertTrue(np.all(pd.notnull(self.meth_opt.atom)))
        self.nap_opt.parse_atom()
        self.assertEqual(self.nap_opt.atom.shape[0], 806)
        self.assertTrue(np.all(pd.notnull(self.nap_opt.atom)))

    def test_parse_basis_set(self):
        self.uo2.parse_basis_set()
        self.assertEqual(self.uo2.basis_set.shape[0], 49)
        self.assertTrue(np.all(pd.notnull(self.uo2.basis_set)))
        self.mam3.parse_basis_set()
        self.assertEqual(self.mam3.basis_set.shape[0], 32)
        self.assertTrue(np.all(pd.notnull(self.mam3.basis_set)))
        self.mam4.parse_basis_set()
        self.assertEqual(self.mam4.basis_set.shape[0], 53)
        self.assertTrue(np.all(pd.notnull(self.mam4.basis_set)))

    def test_parse_orbital(self):
        self.uo2.parse_orbital()
        self.assertEqual(self.uo2.orbital.shape[0], 141)
        self.assertTrue(np.all(pd.notnull(self.uo2.orbital)))
        self.mam3.parse_orbital()
        self.assertEqual(self.mam3.orbital.shape[0], 28)
        self.assertTrue(np.all(pd.notnull(self.mam3.orbital)))
        self.mam4.parse_orbital()
        self.assertEqual(self.mam4.orbital.shape[0], 91)
        self.assertTrue(np.all(pd.notnull(self.mam4.orbital)))
        self.meth_opt.parse_orbital()
        self.assertEqual(self.meth_opt.orbital.shape[0], 160)
        self.assertTrue(np.all(pd.notnull(self.meth_opt.orbital)))
        self.nap_tddft.parse_orbital()
        self.assertEqual(self.nap_tddft.orbital.shape[0], 611)
        self.assertTrue(np.all(pd.notnull(self.nap_tddft.orbital)))

    def test_parse_momatrix(self):
        self.uo2.parse_momatrix()
        self.assertEqual(self.uo2.momatrix.shape[0], 19881)
        self.assertTrue(np.all(pd.notnull(self.uo2.momatrix)))
        self.mam3.parse_momatrix()
        self.assertEqual(self.mam3.momatrix.shape[0], 784)
        self.assertTrue(np.all(pd.notnull(self.mam3.momatrix)))
        self.mam4.parse_momatrix()
        self.assertEqual(self.mam4.momatrix.shape[0], 8281)
        self.assertTrue(np.all(pd.notnull(self.mam4.momatrix)))

    def test_parse_basis_set_order(self):
        self.uo2.parse_basis_set_order()
        self.assertEqual(self.uo2.basis_set_order.shape[0], 141)
        self.assertTrue(np.all(pd.notnull(self.uo2.basis_set_order)))
        self.mam3.parse_basis_set_order()
        self.assertEqual(self.mam3.basis_set_order.shape[0], 28)
        self.assertTrue(np.all(pd.notnull(self.mam3.basis_set_order)))
        self.mam4.parse_basis_set_order()
        self.assertEqual(self.mam4.basis_set_order.shape[0], 91)
        self.assertTrue(np.all(pd.notnull(self.mam4.basis_set_order)))

    def test_parse_frame(self):
        self.uo2.parse_frame()
        self.assertEqual(self.uo2.frame.shape[0], 1)
        self.assertTrue(np.all(pd.notnull(self.uo2.frame)))
        self.mam3.parse_frame()
        self.assertEqual(self.mam3.frame.shape[0], 1)
        self.assertTrue(np.all(pd.notnull(self.mam3.frame)))
        self.mam4.parse_frame()
        self.assertEqual(self.mam4.frame.shape[0], 1)
        self.assertTrue(np.all(pd.notnull(self.mam4.frame)))
        self.meth_opt.parse_frame()
        self.assertEqual(self.meth_opt.frame.shape[0], 12)
        self.assertTrue(np.all(pd.notnull(self.meth_opt.frame)))
        self.nap_opt.parse_frame()
        self.assertEqual(self.nap_opt.frame.shape[0], 26)
        self.assertTrue(np.all(pd.notnull(self.nap_opt.frame)))

    def test_parse_frequency(self):
        self.meth_freq.parse_frequency()
        self.assertEqual(self.meth_freq.frequency.shape[0], 240)
        self.assertTrue(np.all(pd.notnull(self.meth_freq.frequency)))

    def test_parse_excitation(self):
        self.nap_tddft.parse_excitation()
        self.assertEqual(self.nap_tddft.excitation.shape[0], 15)
        self.assertTrue(np.all(pd.notnull(self.nap_tddft.excitation)))
        self.h2o2_tddft.parse_excitation()
        self.assertEqual(self.h2o2_tddft.excitation.shape[0], 32)
        self.assertTrue(np.all(pd.notnull(self.h2o2_tddft.excitation)))

    def test_shielding_tensor(self):
        # TODO: add this thing
        pass

    def test_to_universe(self):
        """Test the to_universe method."""
        uo2 = self.uo2.to_universe(ignore=True)
        mam3 = self.mam3.to_universe(ignore=True)
        #meth_opt = self.meth_opt.to_universe(ignore=True)
        for uni in [uo2, mam3]:
            for attr in ['atom', 'basis_set', 'basis_set_order',
                         'momatrix', 'orbital', 'frame']:
                self.assertTrue(hasattr(uni, attr))
<|endoftext|>