<commit_before>#!/usr/bin/env python
"""
insta_raider.py

usage: insta_raider.py [-h] -u USERNAME

@amirkurtovic

"""
import argparse
import logging
import os
import os.path as op
import re
import email.utils as eut
import requests
import calendar
try:
    from requests.packages.urllib3.exceptions import InsecurePlatformWarning
except ImportError:
    from urllib3.exceptions import InsecurePlatformWarning
import time
import warnings
import selenium.webdriver as webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.common.exceptions import NoSuchElementException
import json
from datetime import datetime
try:
    from gi.repository import GExiv2
except ImportError:
    GExiv2 = None

warnings.filterwarnings("ignore", category=InsecurePlatformWarning)

class PrivateUserError(Exception):
    """Raised if the profile is found to be private"""

try:
    import urlparse
except ImportError:
    import urllib.parse as urlparse

class InstaRaider(object):

    def __init__(self, username, directory, num_to_download=None,
                 log_level='info', use_metadata=False, get_videos=False):
        self.username = username
        self.profile_url = self.get_url(username)
        self.directory = directory
        self.PAUSE = 1
        self.user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
        self.headers = {'User-Agent': self.user_agent}
        self.html_source = None
        self.log_level = getattr(logging, log_level.upper())
        self.setup_logging(self.log_level)
        self.use_metadata = use_metadata
        self.get_videos = get_videos
        self.set_num_posts(num_to_download)
        self.setup_webdriver()

    def __del__(self):
        if self.webdriver:
            self.webdriver.close()

    def get_url(self, path):
        return urlparse.urljoin('https://instagram.com', path)

    def set_num_posts(self, num_to_download=None):
        self.num_posts = int(self.get_posts_count(self.profile_url) or 0)
        self.num_to_download = num_to_download

    def setup_logging(self, level=logging.INFO):
        self.logger = logging.getLogger('instaraider')
        self.logger.addHandler(logging.StreamHandler())
        self.logger.setLevel(logging.INFO)

    def log(self, *strings, **kwargs):
        level = kwargs.pop('level', logging.INFO)
        self.logger.log(level, u' '.join(str(s) for s in strings))

    def setup_webdriver(self):
        self.profile = webdriver.FirefoxProfile()
        self.profile.set_preference("general.useragent.override", self.user_agent)
        self.webdriver = webdriver.Firefox(self.profile)
        self.webdriver.set_window_size(480, 320)
        self.webdriver.set_window_position(800, 0)

    def get_posts_count(self, url):
        """
        Given a url to Instagram profile, return number of photos posted
        """
        response = requests.get(url)
        counts_code = re.search(r'\"media":{"count":\d+', response.text)
        if not counts_code:
            return None
        return re.findall(r'\d+', counts_code.group())[0]

    def log_in_user(self):
        driver = self.webdriver
        self.log('You need to login to access this profile.',
                 'Redirecting you to the login page in the browser.',
                 level=logging.WARN)
        driver.get(self.get_url('accounts/login/'))

        # Wait until user has been successfully logged in and redirceted
        # to his/her feed.
        WebDriverWait(driver, 60).until(
            expected_conditions.presence_of_element_located(
                (By.CSS_SELECTOR, '.-cx-PRIVATE-FeedPage__feed'),
            )
        )

        self.log('User successfully logged in.', level=logging.INFO)
        self.set_num_posts()  # Have to set this again
        driver.get(self.profile_url)

    def load_instagram(self):
        """
        Using Selenium WebDriver, load Instagram page to get page source

        """
        self.log(self.username, 'has', self.num_posts, 'posts on Instagram.')
        if self.num_to_download is not None:
            self.log("The first", self.num_to_download, "of them will be downloaded.")

        num_to_download = self.num_to_download or self.num_posts
        driver = self.webdriver
        # load Instagram profile and wait for PAUSE
        self.log("Loading Instagram profile...")
        driver.get(self.profile_url)
        driver.implicitly_wait(self.PAUSE)

        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        try:
            el = driver.find_element_by_css_selector(
                '.-cx-PRIVATE-ProfilePage__advisoryMessageHeader'
            )
        except NoSuchElementException:
            pass
        else:
            self.log_in_user()

        if (num_to_download > 24):
            scroll_to_bottom = self.get_scroll_count(num_to_download)
            element = driver.find_element_by_css_selector('div.o98')
            driver.implicitly_wait(self.PAUSE)
            element.click()

            for y in range(int(scroll_to_bottom)):
                self.scroll_page(driver)

        # After load all profile photos, retur, source to download_photos()
        time.sleep(1)
        source = driver.page_source

        return source

    def scroll_page(self, driver):
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(0.2)
        driver.execute_script("window.scrollTo(0, 0);")

    def get_scroll_count(self, count):
        return (int(count) - 24) / 12 + 1

    def validate(self):
        """
        returns True if Instagram username is valid
        """
        req = requests.get(self.profile_url)

        try:
            req.raise_for_status()
        except:
            self.log('User', self.username, 'is not valid.',
                     level=logging.ERROR)
            return False

        if not self.num_posts:
            self.log('User', self.username, 'has no photos to download.',
                     level=logging.ERROR)
            return False
        return True

    def save_photo(self, photo_url, photo_name):
        image_request = requests.get(photo_url, headers=self.headers)
        image_data = image_request.content
        with open(photo_name, 'wb') as fp:
            fp.write(image_data)
        return image_request.headers

    def download_photos(self):
        """
        Given source code for loaded Instagram page,
        extract all hrefs and download full-resolution photos

        source: HTML source code of Instagram profile papge
        """
        num_to_download = self.num_to_download or self.num_posts
        if self.html_source is None:
            self.html_source = self.load_instagram()

        # check if directory exists, if not, make it
        if not op.exists(self.directory):
            os.makedirs(self.directory)

        # index for progress bar
        photos_saved = 0
        self.log("Saving photos to", self.directory)

        links = self.find_links()

        for link in links:
            photo_url = link['display_src']
            photo_url = photo_url.replace('\\', '')
            photo_url = re.sub(r'/s\d+x\d+/', '/', photo_url)
            caption = link.get('caption')
            date_time = link.get('date_time')
            photo_basename = op.basename(photo_url)
            photo_name = op.join(self.directory, photo_basename)

            # save full-resolution photo if its new
            if not op.isfile(photo_name):
                headers = self.save_photo(photo_url, photo_name)
                photos_saved += 1
                self.log('Downloaded file {}/{} ({}).'.format(
                    photos_saved, num_to_download, photo_basename))
                # put info from Instagram post into image metadata
                if self.use_metadata:
                    self.add_metadata(photo_name, caption, date_time)
                if "last-modified" in headers:
                    modtime = calendar.timegm(eut.parsedate(headers["last-modified"]))
                    os.utime(photo_name, (modtime, modtime))
            else:
                self.log('Skipping file', photo_name, 'as it already exists.')

            if photos_saved >= num_to_download:
                break

        self.log('Saved', photos_saved, 'files to', self.directory)

    def find_links(self):
        """
        Find all image urls/metadata in html_source.

        Returns a list of dicts.
        e.g., [{'display_src': 'http://image.url',
                'caption': 'some text from Instagram post',
                'date_time': '1448420058.0'},
               {...},]
        'display_src' must be present in each dict;
            the other keys are optional.
        """
        photos = []
        if self.use_metadata:
            if not GExiv2:
                self.use_metadata = False
                self.log('GExiv2 python module not found.',
                         'Images will not be tagged.')
        try:
            json_data = re.search(r'(?s)<script [^>]*>window\._sharedData'
                                  r'.*?"nodes".+?</script>',
                                  self.html_source)
            json_data = re.search(r'{.+}', json_data.group(0))
            json_data = json.loads(json_data.group(0))
            photos = list(gen_dict_extract('nodes', json_data))[0]
            # find profile_pic
            profile_pic = list(gen_dict_extract('profile_pic_url', json_data))
            if profile_pic:
                # todo (possible):
                #   add a key in the dict to indicate this is profile_pic.
                #   then we could also name the file "profile.jpg" or similar
                #   and also not include it in photos_saved so if user
                #   uses -n N to download some number of images, he still gets
                #   the first N images rather than N-1 images plus
                #   profile_pic
                profile_pic = [{'display_src': p} for p in profile_pic[:1]]
                photos = profile_pic + photos
        except:
            if self.use_metadata:
                self.use_metadata = False
                self.log('Could not find any image metadata.',
                         'Photos will not be tagged.')
            links = re.finditer(r'src="([https]+:...[\/\w \.-]*..[\/\w \.-]*'
                                r'..[\/\w \.-]*..[\/\w \.-].jpg)',
                                self.html_source)
            photos = [{'display_src': m.group(1)} for m in links]
        for photo in photos:
            date = photo.get('date')
            if date:
                try:
                    photo['date_time'] = datetime.fromtimestamp(date)
                except:
                    photo['date_time'] = None
        return photos

    def download_videos(self):
        """
        Given source code for loaded Instagram page:
        - discover all video wrapper links
        - activate all links to load video url
        - extract and download video url
        """

        if not self.get_videos:
            return;

        # We need to use the driver to query the video wrappers
        driver = self.webdriver

        num_to_download = self.num_to_download or self.num_posts
        if self.html_source is None:
            self.html_source = self.load_instagram()
        if not op.exists(self.directory):
            os.makedirs(self.directory)

        videos_saved = 0
        self.log("Saving videos to", self.directory)

        # Find all of the video wrappers
        video_wrapper_elements = driver.find_elements_by_xpath('.//*[@id="react-root"]/section/main/article/div/div[1]/div/a[.//*[@Class="w79 f99"]]')
        video_wrapper_urls = [link.get_attribute('href') for link in video_wrapper_elements]

        for video_wrapper in video_wrapper_urls:
            # Fetch the link of the video wrapper
            driver.get(video_wrapper)

            # Wait until the real video appears
            WebDriverWait(driver, 60).until(
                expected_conditions.presence_of_all_elements_located(
                    (By.CLASS_NAME, 's68')
                )
            )

            # Get the real video, since only 1 video can be clicked on at a time, we only expect there to be a single result
            video_elements = driver.find_elements_by_class_name('s68')
            if len(video_elements) > 0:
                video_url = video_elements[0].get_attribute('src')
                video_name = op.join(self.directory, video_url.split('/')[len(video_url.split('/')) - 1])

                if not op.isfile(video_name):
                    self.save_photo(video_url, video_name)
                    videos_saved += 1
                    self.log('Downloaded file {}/{} ({}).'.format(
                        videos_saved, num_to_download, op.basename(video_name)))
                else:
                    self.log('Skipping file', video_name, 'as it already exists.')

                if videos_saved >= num_to_download:
                    break

        self.log('Saved', videos_saved, 'videos to', self.directory)

    def add_metadata(self, photo_name, caption, date_time):
        """
        Tag downloaded photos with metadata from associated Instagram post.

        If GExiv2 is not installed, do nothing.
        """
        if GExiv2:
            if caption or date_time:
                # todo: improve error handling
                try:
                    exif = GExiv2.Metadata(photo_name)
                    if caption:
                        try:
                            exif.set_comment(caption)
                        except:
                            self.log('Error setting image caption metadata.')
                    if date_time:
                        try:
                            exif.set_date_time(date_time)
                        except:
                            self.log('Error setting image date metadata.')
                    exif.save_file()
                except:
                    pass

def gen_dict_extract(key, var):
    """
    Recursively search for given dict key in nested dicts.

    from http://stackoverflow.com/a/29652561
    author: hexerei software
    """
    if hasattr(var,'iteritems'):
        for k, v in var.iteritems():
            if k == key:
                yield v
            if isinstance(v, dict):
                for result in gen_dict_extract(key, v):
                    yield result
            elif isinstance(v, list):
                for d in v:
                    for result in gen_dict_extract(key, d):
                        yield result

def main():
    # parse arguments
    parser = argparse.ArgumentParser(description='InstaRaider')
    parser.add_argument('username', help='Instagram username')
    parser.add_argument('directory', help='Where to save the images')
    parser.add_argument('-n', '--num-to-download',
                        help='Number of posts to download', type=int)
    parser.add_argument('-l', '--log-level', help="Log level", default='info')
    parser.add_argument('-m', '--add_metadata',
                        help=("Add metadata (caption/date) from Instagram "
                              "post into downloaded images' exif tags "
                              "(requires GExiv2 python module)"),
                        action='store_true', dest='use_metadata')
    parser.add_argument('-v', '--get_videos',
                        help=("Download videos"),
                        action='store_true', dest='get_videos')
    args = parser.parse_args()
    username = args.username
    directory = op.expanduser(args.directory)

    raider = InstaRaider(username, directory,
                         num_to_download=args.num_to_download,
                         log_level=args.log_level,
                         use_metadata=args.use_metadata,
                         get_videos=args.get_videos)

    if not raider.validate():
        return

    raider.download_photos()
    raider.download_videos()

if __name__ == '__main__':
    main()
<commit_msg>Add regex to fetch original photo in some urls<commit_after>#!/usr/bin/env python
"""
insta_raider.py

usage: insta_raider.py [-h] -u USERNAME

@amirkurtovic

"""
import argparse
import logging
import os
import os.path as op
import re
import email.utils as eut
import requests
import calendar
try:
    from requests.packages.urllib3.exceptions import InsecurePlatformWarning
except ImportError:
    from urllib3.exceptions import InsecurePlatformWarning
import time
import warnings
import selenium.webdriver as webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.common.exceptions import NoSuchElementException
import json
from datetime import datetime
try:
    from gi.repository import GExiv2
except ImportError:
    GExiv2 = None

warnings.filterwarnings("ignore", category=InsecurePlatformWarning)

class PrivateUserError(Exception):
    """Raised if the profile is found to be private"""

try:
    import urlparse
except ImportError:
    import urllib.parse as urlparse

class InstaRaider(object):

    def __init__(self, username, directory, num_to_download=None,
                 log_level='info', use_metadata=False, get_videos=False):
        self.username = username
        self.profile_url = self.get_url(username)
        self.directory = directory
        self.PAUSE = 1
        self.user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
        self.headers = {'User-Agent': self.user_agent}
        self.html_source = None
        self.log_level = getattr(logging, log_level.upper())
        self.setup_logging(self.log_level)
        self.use_metadata = use_metadata
        self.get_videos = get_videos
        self.set_num_posts(num_to_download)
        self.setup_webdriver()

    def __del__(self):
        if self.webdriver:
            self.webdriver.close()

    def get_url(self, path):
        return urlparse.urljoin('https://instagram.com', path)

    def set_num_posts(self, num_to_download=None):
        self.num_posts = int(self.get_posts_count(self.profile_url) or 0)
        self.num_to_download = num_to_download

    def setup_logging(self, level=logging.INFO):
        self.logger = logging.getLogger('instaraider')
        self.logger.addHandler(logging.StreamHandler())
        self.logger.setLevel(logging.INFO)

    def log(self, *strings, **kwargs):
        level = kwargs.pop('level', logging.INFO)
        self.logger.log(level, u' '.join(str(s) for s in strings))

    def setup_webdriver(self):
        self.profile = webdriver.FirefoxProfile()
        self.profile.set_preference("general.useragent.override", self.user_agent)
        self.webdriver = webdriver.Firefox(self.profile)
        self.webdriver.set_window_size(480, 320)
        self.webdriver.set_window_position(800, 0)

    def get_posts_count(self, url):
        """
        Given a url to Instagram profile, return number of photos posted
        """
        response = requests.get(url)
        counts_code = re.search(r'\"media":{"count":\d+', response.text)
        if not counts_code:
            return None
        return re.findall(r'\d+', counts_code.group())[0]

    def log_in_user(self):
        driver = self.webdriver
        self.log('You need to login to access this profile.',
                 'Redirecting you to the login page in the browser.',
                 level=logging.WARN)
        driver.get(self.get_url('accounts/login/'))

        # Wait until user has been successfully logged in and redirceted
        # to his/her feed.
        WebDriverWait(driver, 60).until(
            expected_conditions.presence_of_element_located(
                (By.CSS_SELECTOR, '.-cx-PRIVATE-FeedPage__feed'),
            )
        )

        self.log('User successfully logged in.', level=logging.INFO)
        self.set_num_posts()  # Have to set this again
        driver.get(self.profile_url)

    def load_instagram(self):
        """
        Using Selenium WebDriver, load Instagram page to get page source

        """
        self.log(self.username, 'has', self.num_posts, 'posts on Instagram.')
        if self.num_to_download is not None:
            self.log("The first", self.num_to_download, "of them will be downloaded.")

        num_to_download = self.num_to_download or self.num_posts
        driver = self.webdriver
        # load Instagram profile and wait for PAUSE
        self.log("Loading Instagram profile...")
        driver.get(self.profile_url)
        driver.implicitly_wait(self.PAUSE)

        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        try:
            el = driver.find_element_by_css_selector(
                '.-cx-PRIVATE-ProfilePage__advisoryMessageHeader'
            )
        except NoSuchElementException:
            pass
        else:
            self.log_in_user()

        if (num_to_download > 24):
            scroll_to_bottom = self.get_scroll_count(num_to_download)
            element = driver.find_element_by_css_selector('div.o98')
            driver.implicitly_wait(self.PAUSE)
            element.click()

            for y in range(int(scroll_to_bottom)):
                self.scroll_page(driver)

        # After load all profile photos, retur, source to download_photos()
        time.sleep(1)
        source = driver.page_source

        return source

    def scroll_page(self, driver):
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(0.2)
        driver.execute_script("window.scrollTo(0, 0);")

    def get_scroll_count(self, count):
        return (int(count) - 24) / 12 + 1

    def validate(self):
        """
        returns True if Instagram username is valid
        """
        req = requests.get(self.profile_url)

        try:
            req.raise_for_status()
        except:
            self.log('User', self.username, 'is not valid.',
                     level=logging.ERROR)
            return False

        if not self.num_posts:
            self.log('User', self.username, 'has no photos to download.',
                     level=logging.ERROR)
            return False
        return True

    def save_photo(self, photo_url, photo_name):
        image_request = requests.get(photo_url, headers=self.headers)
        image_data = image_request.content
        with open(photo_name, 'wb') as fp:
            fp.write(image_data)
        return image_request.headers

    def download_photos(self):
        """
        Given source code for loaded Instagram page,
        extract all hrefs and download full-resolution photos

        source: HTML source code of Instagram profile papge
        """
        num_to_download = self.num_to_download or self.num_posts
        if self.html_source is None:
            self.html_source = self.load_instagram()

        # check if directory exists, if not, make it
        if not op.exists(self.directory):
            os.makedirs(self.directory)

        # index for progress bar
        photos_saved = 0
        self.log("Saving photos to", self.directory)

        links = self.find_links()

        for link in links:
            photo_url = link['display_src']
            photo_url = photo_url.replace('\\', '')
            photo_url = re.sub(r'/s\d+x\d+/', '/', photo_url)
            photo_url = re.sub(r'/\w+\.\d+\.\d+\.\d+/', '/', photo_url)
            caption = link.get('caption')
            date_time = link.get('date_time')
            photo_basename = op.basename(photo_url)
            photo_name = op.join(self.directory, photo_basename)

            # save full-resolution photo if its new
            if not op.isfile(photo_name):
                headers = self.save_photo(photo_url, photo_name)
                photos_saved += 1
                self.log('Downloaded file {}/{} ({}).'.format(
                    photos_saved, num_to_download, photo_basename))
                # put info from Instagram post into image metadata
                if self.use_metadata:
                    self.add_metadata(photo_name, caption, date_time)
                if "last-modified" in headers:
                    modtime = calendar.timegm(eut.parsedate(headers["last-modified"]))
                    os.utime(photo_name, (modtime, modtime))
            else:
                self.log('Skipping file', photo_name, 'as it already exists.')

            if photos_saved >= num_to_download:
                break

        self.log('Saved', photos_saved, 'files to', self.directory)

    def find_links(self):
        """
        Find all image urls/metadata in html_source.

        Returns a list of dicts.
        e.g., [{'display_src': 'http://image.url',
                'caption': 'some text from Instagram post',
                'date_time': '1448420058.0'},
               {...},]
        'display_src' must be present in each dict;
            the other keys are optional.
        """
        photos = []
        if self.use_metadata:
            if not GExiv2:
                self.use_metadata = False
                self.log('GExiv2 python module not found.',
                         'Images will not be tagged.')
        try:
            json_data = re.search(r'(?s)<script [^>]*>window\._sharedData'
                                  r'.*?"nodes".+?</script>',
                                  self.html_source)
            json_data = re.search(r'{.+}', json_data.group(0))
            json_data = json.loads(json_data.group(0))
            photos = list(gen_dict_extract('nodes', json_data))[0]
            # find profile_pic
            profile_pic = list(gen_dict_extract('profile_pic_url', json_data))
            if profile_pic:
                # todo (possible):
                #   add a key in the dict to indicate this is profile_pic.
                #   then we could also name the file "profile.jpg" or similar
                #   and also not include it in photos_saved so if user
                #   uses -n N to download some number of images, he still gets
                #   the first N images rather than N-1 images plus
                #   profile_pic
                profile_pic = [{'display_src': p} for p in profile_pic[:1]]
                photos = profile_pic + photos
        except:
            if self.use_metadata:
                self.use_metadata = False
                self.log('Could not find any image metadata.',
                         'Photos will not be tagged.')
            links = re.finditer(r'src="([https]+:...[\/\w \.-]*..[\/\w \.-]*'
                                r'..[\/\w \.-]*..[\/\w \.-].jpg)',
                                self.html_source)
            photos = [{'display_src': m.group(1)} for m in links]
        for photo in photos:
            date = photo.get('date')
            if date:
                try:
                    photo['date_time'] = datetime.fromtimestamp(date)
                except:
                    photo['date_time'] = None
        return photos

    def download_videos(self):
        """
        Given source code for loaded Instagram page:
        - discover all video wrapper links
        - activate all links to load video url
        - extract and download video url
        """

        if not self.get_videos:
            return;

        # We need to use the driver to query the video wrappers
        driver = self.webdriver

        num_to_download = self.num_to_download or self.num_posts
        if self.html_source is None:
            self.html_source = self.load_instagram()
        if not op.exists(self.directory):
            os.makedirs(self.directory)

        videos_saved = 0
        self.log("Saving videos to", self.directory)

        # Find all of the video wrappers
        video_wrapper_elements = driver.find_elements_by_xpath('.//*[@id="react-root"]/section/main/article/div/div[1]/div/a[.//*[@Class="w79 f99"]]')
        video_wrapper_urls = [link.get_attribute('href') for link in video_wrapper_elements]

        for video_wrapper in video_wrapper_urls:
            # Fetch the link of the video wrapper
            driver.get(video_wrapper)

            # Wait until the real video appears
            WebDriverWait(driver, 60).until(
                expected_conditions.presence_of_all_elements_located(
                    (By.CLASS_NAME, 's68')
                )
            )

            # Get the real video, since only 1 video can be clicked on at a time, we only expect there to be a single result
            video_elements = driver.find_elements_by_class_name('s68')
            if len(video_elements) > 0:
                video_url = video_elements[0].get_attribute('src')
                video_name = op.join(self.directory, video_url.split('/')[len(video_url.split('/')) - 1])

                if not op.isfile(video_name):
                    self.save_photo(video_url, video_name)
                    videos_saved += 1
                    self.log('Downloaded file {}/{} ({}).'.format(
                        videos_saved, num_to_download, op.basename(video_name)))
                else:
                    self.log('Skipping file', video_name, 'as it already exists.')

                if videos_saved >= num_to_download:
                    break

        self.log('Saved', videos_saved, 'videos to', self.directory)

    def add_metadata(self, photo_name, caption, date_time):
        """
        Tag downloaded photos with metadata from associated Instagram post.

        If GExiv2 is not installed, do nothing.
        """
        if GExiv2:
            if caption or date_time:
                # todo: improve error handling
                try:
                    exif = GExiv2.Metadata(photo_name)
                    if caption:
                        try:
                            exif.set_comment(caption)
                        except:
                            self.log('Error setting image caption metadata.')
                    if date_time:
                        try:
                            exif.set_date_time(date_time)
                        except:
                            self.log('Error setting image date metadata.')
                    exif.save_file()
                except:
                    pass

def gen_dict_extract(key, var):
    """
    Recursively search for given dict key in nested dicts.

    from http://stackoverflow.com/a/29652561
    author: hexerei software
    """
    if hasattr(var,'iteritems'):
        for k, v in var.iteritems():
            if k == key:
                yield v
            if isinstance(v, dict):
                for result in gen_dict_extract(key, v):
                    yield result
            elif isinstance(v, list):
                for d in v:
                    for result in gen_dict_extract(key, d):
                        yield result

def main():
    # parse arguments
    parser = argparse.ArgumentParser(description='InstaRaider')
    parser.add_argument('username', help='Instagram username')
    parser.add_argument('directory', help='Where to save the images')
    parser.add_argument('-n', '--num-to-download',
                        help='Number of posts to download', type=int)
    parser.add_argument('-l', '--log-level', help="Log level", default='info')
    parser.add_argument('-m', '--add_metadata',
                        help=("Add metadata (caption/date) from Instagram "
                              "post into downloaded images' exif tags "
                              "(requires GExiv2 python module)"),
                        action='store_true', dest='use_metadata')
    parser.add_argument('-v', '--get_videos',
                        help=("Download videos"),
                        action='store_true', dest='get_videos')
    args = parser.parse_args()
    username = args.username
    directory = op.expanduser(args.directory)

    raider = InstaRaider(username, directory,
                         num_to_download=args.num_to_download,
                         log_level=args.log_level,
                         use_metadata=args.use_metadata,
                         get_videos=args.get_videos)

    if not raider.validate():
        return

    raider.download_photos()
    raider.download_videos()

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

# @mr-c snippets build configuration file, created by
# `ablog start` on Wed Oct  7 02:03:27 2015.
#
# Note that not all possible configuration values are present in this file.
# All configuration values have a default; values that are commented out
# serve to show the default.

import os
import sys
import ablog
import alabaster

# -- General ABlog Options ----------------------------------------------------

# A path relative to the configuration directory for blog archive pages.
#blog_path = 'blog'

# The “title” for the blog, used in acthive pages.  Default is ``'Blog'``.
blog_title = u'@mr-c snippets Blog'

# Base URL for the website, required for generating feeds.
# e.g. blog_baseurl = "http://example.com/"
blog_baseurl = u'True'

# Choose to archive only post titles. Archiving only titles can speed
# up project building.
#blog_archive_titles = False

# -- Blog Authors, Languages, and Locations -----------------------------------

# A dictionary of author names mapping to author full display names and
# links. Dictionary keys are what should be used in ``post`` directive
# to refer to the author.  Default is ``{}``.
blog_authors = {
    'Michael R. Crusoe': ('Michael R. Crusoe', None),
}

# A dictionary of language code names mapping to full display names and
# links of these languages. Similar to :confval:`blog_authors`, dictionary
# keys should be used in ``post`` directive to refer to the locations.
# Default is ``{}``.
#blog_languages = {
#    'en': ('English', None),
#}

# A dictionary of location names mapping to full display names and
# links of these locations. Similar to :confval:`blog_authors`, dictionary
# keys should be used in ``post`` directive to refer to the locations.
# Default is ``{}``.
#blog_locations = {
#    'Earth': ('The Blue Planet', 'http://en.wikipedia.org/wiki/Earth),
#}

# -- Blog Post Related --------------------------------------------------------

# post_date_format = '%b %d, %Y'

# Number of paragraphs (default is ``1``) that will be displayed as an excerpt
# from the post. Setting this ``0`` will result in displaying no post excerpt
# in archive pages.  This option can be set on a per post basis using
#post_auto_excerpt = 1

# Index of the image that will be displayed in the excerpt of the post.
# Default is ``0``, meaning no image.  Setting this to ``1`` will include
# the first image, when available, to the excerpt.  This option can be set
# on a per post basis using :rst:dir:`post` directive option ``image``.
#post_auto_image = 0

# Number of seconds (default is ``5``) that a redirect page waits before
# refreshing the page to redirect to the post.
#post_redirect_refresh = 5

# When ``True``, post title and excerpt is always taken from the section that
# contains the :rst:dir:`post` directive, instead of the document. This is the
# behavior when :rst:dir:`post` is used multiple times in a document. Default
# is ``False``.
#post_always_section = False

# -- ABlog Sidebars -------------------------------------------------------

# There are seven sidebars you can include in your HTML output.
# postcard.html provides information regarding the current post.
# recentposts.html lists most recent five posts. Others provide
# a link to a archive pages generated for each tag, category, and year.
# In addition, there are authors.html, languages.html, and locations.html
# sidebars that link to author and location archive pages.
html_sidebars = {
    '**': [ 'about.html',
            'postcard.html', 'navigation.html',
            'recentposts.html', 'tagcloud.html',
            'categories.html',  'archives.html',
            'searchbox.html',
            ],
    }

# -- Blog Feed Options --------------------------------------------------------

# Turn feeds by setting :confval:`blog_baseurl` configuration variable.
# Choose to create feeds per author, location, tag, category, and year,
# default is ``False``.
blog_feed_archives = True

# Choose to display full text in blog feeds, default is ``False``.
blog_feed_fulltext = True

# Blog feed subtitle, default is ``None``.
#blog_feed_subtitle = None

# Choose to feed only post titles, default is ``False``.
#blog_feed_titles = False

# Specify number of recent posts to include in feeds, default is ``None``
# for all posts.
#blog_feed_length = None

# -- Font-Awesome Options -----------------------------------------------------

# ABlog templates will use of Font Awesome icons if one of the following
# is ``True``

# Link to `Font Awesome`_ at `Bootstrap CDN`_ and use icons in sidebars
# and post footers.  Default: ``False``
fontawesome_link_cdn = True

# Sphinx_ theme already links to `Font Awesome`_.  Default: ``False``
#fontawesome_included = False

# Alternatively, you can provide the path to `Font Awesome`_ :file:`.css`
# with the configuration option: fontawesome_css_file
# Path to `Font Awesome`_ :file:`.css` (default is ``None``) that will
# be linked to in HTML output by ABlog.
#fontawesome_css_file = None

# -- Disqus Integration -------------------------------------------------------

# You can enable Disqus_ by setting ``disqus_shortname`` variable.
# Disqus_ short name for the blog.
#disqus_shortname = None

# Choose to disqus pages that are not posts, default is ``False``.
#disqus_pages = False

# Choose to disqus posts that are drafts (without a published date),
# default is ``False``.
#disqus_drafts = False

# -- Sphinx Options -----------------------------------------------------------

# If your project needs a minimal Sphinx version, state it here.
needs_sphinx = '1.2'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'sphinx.ext.extlinks',
    'sphinx.ext.intersphinx',
    'sphinx.ext.todo',
    'alabaster',
    'ablog',
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates', ablog.get_html_templates_path()]

# The suffix(es) of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'@mr-c snippets'
copyright = u'2015, Michael R. Crusoe'
author = u'Michael R. Crusoe'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = ''
# The full version, including alpha/beta/rc tags.
release = ''

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["env"]

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'alabaster'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
html_theme_options = {
    'github_button': False,
}

# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = [alabaster.get_path()]

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
#html_search_language = 'en'

# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}

# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'

# Output file base name for HTML help builder.
htmlhelp_basename = 'mr-csnippetsdoc'

<commit_msg>set base URL<commit_after># -*- coding: utf-8 -*-

# @mr-c snippets build configuration file, created by
# `ablog start` on Wed Oct  7 02:03:27 2015.
#
# Note that not all possible configuration values are present in this file.
# All configuration values have a default; values that are commented out
# serve to show the default.

import os
import sys
import ablog
import alabaster

# -- General ABlog Options ----------------------------------------------------

# A path relative to the configuration directory for blog archive pages.
#blog_path = 'blog'

# The “title” for the blog, used in acthive pages.  Default is ``'Blog'``.
blog_title = u'@mr-c snippets Blog'

# Base URL for the website, required for generating feeds.
# e.g. blog_baseurl = "http://example.com/"
blog_baseurl = u'http://mrc.commonwl.org/'

# Choose to archive only post titles. Archiving only titles can speed
# up project building.
#blog_archive_titles = False

# -- Blog Authors, Languages, and Locations -----------------------------------

# A dictionary of author names mapping to author full display names and
# links. Dictionary keys are what should be used in ``post`` directive
# to refer to the author.  Default is ``{}``.
blog_authors = {
    'Michael R. Crusoe': ('Michael R. Crusoe', None),
}

# A dictionary of language code names mapping to full display names and
# links of these languages. Similar to :confval:`blog_authors`, dictionary
# keys should be used in ``post`` directive to refer to the locations.
# Default is ``{}``.
#blog_languages = {
#    'en': ('English', None),
#}

# A dictionary of location names mapping to full display names and
# links of these locations. Similar to :confval:`blog_authors`, dictionary
# keys should be used in ``post`` directive to refer to the locations.
# Default is ``{}``.
#blog_locations = {
#    'Earth': ('The Blue Planet', 'http://en.wikipedia.org/wiki/Earth),
#}

# -- Blog Post Related --------------------------------------------------------

# post_date_format = '%b %d, %Y'

# Number of paragraphs (default is ``1``) that will be displayed as an excerpt
# from the post. Setting this ``0`` will result in displaying no post excerpt
# in archive pages.  This option can be set on a per post basis using
#post_auto_excerpt = 1

# Index of the image that will be displayed in the excerpt of the post.
# Default is ``0``, meaning no image.  Setting this to ``1`` will include
# the first image, when available, to the excerpt.  This option can be set
# on a per post basis using :rst:dir:`post` directive option ``image``.
#post_auto_image = 0

# Number of seconds (default is ``5``) that a redirect page waits before
# refreshing the page to redirect to the post.
#post_redirect_refresh = 5

# When ``True``, post title and excerpt is always taken from the section that
# contains the :rst:dir:`post` directive, instead of the document. This is the
# behavior when :rst:dir:`post` is used multiple times in a document. Default
# is ``False``.
#post_always_section = False

# -- ABlog Sidebars -------------------------------------------------------

# There are seven sidebars you can include in your HTML output.
# postcard.html provides information regarding the current post.
# recentposts.html lists most recent five posts. Others provide
# a link to a archive pages generated for each tag, category, and year.
# In addition, there are authors.html, languages.html, and locations.html
# sidebars that link to author and location archive pages.
html_sidebars = {
    '**': [ 'about.html',
            'postcard.html', 'navigation.html',
            'recentposts.html', 'tagcloud.html',
            'categories.html',  'archives.html',
            'searchbox.html',
            ],
    }

# -- Blog Feed Options --------------------------------------------------------

# Turn feeds by setting :confval:`blog_baseurl` configuration variable.
# Choose to create feeds per author, location, tag, category, and year,
# default is ``False``.
blog_feed_archives = True

# Choose to display full text in blog feeds, default is ``False``.
blog_feed_fulltext = True

# Blog feed subtitle, default is ``None``.
#blog_feed_subtitle = None

# Choose to feed only post titles, default is ``False``.
#blog_feed_titles = False

# Specify number of recent posts to include in feeds, default is ``None``
# for all posts.
#blog_feed_length = None

# -- Font-Awesome Options -----------------------------------------------------

# ABlog templates will use of Font Awesome icons if one of the following
# is ``True``

# Link to `Font Awesome`_ at `Bootstrap CDN`_ and use icons in sidebars
# and post footers.  Default: ``False``
fontawesome_link_cdn = True

# Sphinx_ theme already links to `Font Awesome`_.  Default: ``False``
#fontawesome_included = False

# Alternatively, you can provide the path to `Font Awesome`_ :file:`.css`
# with the configuration option: fontawesome_css_file
# Path to `Font Awesome`_ :file:`.css` (default is ``None``) that will
# be linked to in HTML output by ABlog.
#fontawesome_css_file = None

# -- Disqus Integration -------------------------------------------------------

# You can enable Disqus_ by setting ``disqus_shortname`` variable.
# Disqus_ short name for the blog.
#disqus_shortname = None

# Choose to disqus pages that are not posts, default is ``False``.
#disqus_pages = False

# Choose to disqus posts that are drafts (without a published date),
# default is ``False``.
#disqus_drafts = False

# -- Sphinx Options -----------------------------------------------------------

# If your project needs a minimal Sphinx version, state it here.
needs_sphinx = '1.2'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'sphinx.ext.extlinks',
    'sphinx.ext.intersphinx',
    'sphinx.ext.todo',
    'alabaster',
    'ablog',
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates', ablog.get_html_templates_path()]

# The suffix(es) of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'@mr-c snippets'
copyright = u'2015, Michael R. Crusoe'
author = u'Michael R. Crusoe'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = ''
# The full version, including alpha/beta/rc tags.
release = ''

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["env"]

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'alabaster'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
html_theme_options = {
    'github_button': False,
}

# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = [alabaster.get_path()]

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
#html_search_language = 'en'

# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}

# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'

# Output file base name for HTML help builder.
htmlhelp_basename = 'mr-csnippetsdoc'

<|endoftext|>


<commit_before>import random as r

class Ball(object):

    def __init__(self, renderer):
        self.h = renderer
        self.r = r.randint(20, 40)
        self.vel = PVector(1, 1)*r.uniform(1.0, 4.0)
        self.dir = PVector(-1.5, 1.5)
        self.pos = PVector(r.randint(80, width - 80), r.randint(80, height - 80))
        self.red = (r.randint(100, 255))
        self.green = (r.randint(0, 150))
        self.blue = (r.randint(0, 255))

    def update(self):
        self.pos.x += self.vel.x*self.dir.x
        self.pos.y += self.vel.y*self.dir.y

    def display(self):
        fill(self.red, self.green, self.blue)
        # self.h.noStroke()
        self.h.ellipse(self.pos.x, self.pos.y, self.r*2, self.r*2)

    def checkEdges(self):
        # rechter Rand
        if (self.pos.x > width - 40 - self.r and self.dir.x > 0):
            self.dir.x *= -1
        # linker Rand
        if (self.pos.x < self.r + 40 and self.dir.x < 0):
            self.dir.x *= -1
        # top
        if (self.pos.y < self.r + 40 and self.dir.y < 0):
            self.dir.y *= -1
        # bottom
        if (self.pos.y > height - 40 - self.r and self.dir.y > 0):
            self.dir.y *= -1
<commit_msg>noStroke eliminiert<commit_after>import random as r

class Ball(object):

    def __init__(self, renderer):
        self.h = renderer
        self.r = r.randint(20, 40)
        self.vel = PVector(1, 1)*r.uniform(1.0, 4.0)
        self.dir = PVector(-1.5, 1.5)
        self.pos = PVector(r.randint(80, width - 80), r.randint(80, height - 80))
        self.red = (r.randint(100, 255))
        self.green = (r.randint(0, 150))
        self.blue = (r.randint(0, 255))

    def update(self):
        self.pos.x += self.vel.x*self.dir.x
        self.pos.y += self.vel.y*self.dir.y

    def display(self):
        fill(self.red, self.green, self.blue)
        self.h.ellipse(self.pos.x, self.pos.y, self.r*2, self.r*2)

    def checkEdges(self):
        # rechter Rand
        if (self.pos.x > width - 40 - self.r and self.dir.x > 0):
            self.dir.x *= -1
        # linker Rand
        if (self.pos.x < self.r + 40 and self.dir.x < 0):
            self.dir.x *= -1
        # top
        if (self.pos.y < self.r + 40 and self.dir.y < 0):
            self.dir.y *= -1
        # bottom
        if (self.pos.y > height - 40 - self.r and self.dir.y > 0):
            self.dir.y *= -1
<|endoftext|>


<commit_before>from constructive_hierarchy import *
from hieretikz import all_separations

def print_proof_path(path, proofs):
    for x, y in path:
        if x != y:
            print('\t{} => {}{:>20}'.format(x, y, proofs[(x, y)]))

def examine(a, b, proofs, models):
    separations = all_separations(models)
    connection = is_connected(a, b, frozenset(proofs))
    if connection:
        print('{} => {}'.format(a, b))
        print('Proof:')
        print_proof_path(connection, proofs)
        return
    separation = is_separated(a, b, frozenset(proofs), frozenset(separations))
    if separation:
        presep, postsep = separation
        start = presep[0][0]
        end = postsep[-1][1]
        print('{} =/=> {}'.format(a, b))
        print('Since {} => {}'.format(start, a))
        print_proof_path(presep, proofs)
        print('and {} => {}'.format(b, end))
        print_proof_path(postsep, proofs)
        print('and there is a counter-model showing')
        print('\t{} =/=> {}{:>20}'.format(
            start, end, separations[(start, end)]))
        return
    print("Currently unknown.")
    possible_models = {k for k in models if b in models[k][1]}
    possible_counter_models = {k for k in models if a in models[k][0]}
    if possible_models or possible_counter_models:
        print("A separation would exist if it were shown that")
        if possible_models:
            print("{} holds in {}".format(a, ', or '.join(possible_models)))
        if possible_models and possible_counter_models:
            print("or")
        if possible_counter_models:
            print("{} fails in {}".format(b, ', or '.join(
                possible_counter_models)))

def repl(proofs, models):
    while True:
        try:
            cmd = input('? ')
        except EOFError:
            break
        if not cmd:
            break
        if ',' in cmd:
            cmdlist = list(map(str.strip, cmd.split(',')))
        elif ' ' in cmd:
            cmdlist = cmd.split()
        if len(cmdlist) != 2:
            print('Invalid input.')
        else:
            examine(*cmdlist, proofs, models)
        print()

from drinker import proofs, models
repl(proofs, models)
<commit_msg>Don't suggest hopeless separation possibilities<commit_after>from constructive_hierarchy import *
from hieretikz import all_separations

def print_proof_path(path, proofs):
    for x, y in path:
        if x != y:
            print('\t{} => {}{:>20}'.format(x, y, proofs[(x, y)]))

def examine(a, b, proofs, models):
    separations = all_separations(models)
    connection = is_connected(a, b, frozenset(proofs))
    if connection:
        print('{} => {}'.format(a, b))
        print('Proof:')
        print_proof_path(connection, proofs)
        return
    separation = is_separated(a, b, frozenset(proofs), frozenset(separations))
    if separation:
        presep, postsep = separation
        start = presep[0][0]
        end = postsep[-1][1]
        print('{} =/=> {}'.format(a, b))
        print('Since {} => {}'.format(start, a))
        print_proof_path(presep, proofs)
        print('and {} => {}'.format(b, end))
        print_proof_path(postsep, proofs)
        print('and there is a counter-model showing')
        print('\t{} =/=> {}{:>20}'.format(
            start, end, separations[(start, end)]))
        return
    print("Currently unknown.")
    possible_models = {k for k in models
                       if b in models[k][1] and a not in models[k][1]}
    possible_counter_models = {k for k in models
                               if a in models[k][0] and b not in models[k][0]}
    if possible_models or possible_counter_models:
        print("A separation would exist if it were shown that")
        if possible_models:
            print("{} holds in {}".format(a, ', or '.join(possible_models)))
        if possible_models and possible_counter_models:
            print("or")
        if possible_counter_models:
            print("{} fails in {}".format(b, ', or '.join(
                possible_counter_models)))

def repl(proofs, models):
    while True:
        try:
            cmd = input('? ')
        except EOFError:
            break
        if not cmd:
            break
        if ',' in cmd:
            cmdlist = list(map(str.strip, cmd.split(',')))
        elif ' ' in cmd:
            cmdlist = cmd.split()
        if len(cmdlist) != 2:
            print('Invalid input.')
        else:
            examine(*cmdlist, proofs, models)
        print()

from drinker import proofs, models
repl(proofs, models)
<|endoftext|>


<commit_before>import threading
import logging
import sys
import os

from itertools import product, chain

from sarah.event import Event
from sarah.order import sort_by_successors
from sarah.runtime import get_by_path
from sarah.itertools import pick
from sarah.threading import KeyboardInterruptableEvent

class Module(object):
	def __init__(self, settings, logger):
		for k, v in settings.items():
			setattr(self, k, v)
		self.l = logger
		self.on_settings_changed = dict()

	def change_setting(self, key, value):
		setattr(self, key, value)
		if not key in self.on_settings_changed:
			return
		self.on_settings_changed[key]()

	def register_on_setting_changed(self, key, handler):
		if not key in self.on_settings_changed:
			self.on_settings_changed[key] = Event()
		self.on_settings_changed[key].register(handler)

class Manager(Module):
	def __init__(self, logger=None):
		if logger is None:
			logger = logging.getLogger(object.__repr__(self))
		super(Manager, self).__init__({}, logger)
		self.running = False
		self.modules = dict()
		self.to_stop = list() # objects to stop
		self.daemons = list() # and to join
		self.valueTypes = {'str': str,
				   'float': float,
				   'int': int}
		self.insts = dict()
		# module -> concrete modules implementing module
		self.modules_implementing = dict()
		self.insts_implementing = dict()
		self.add_module_definition('module', ModuleDefinition())
		self.add_module_definition('manager', ModuleDefinition())
		self.register_instance('manager', 'manager', self, {}, {})
		self.sleep_event = KeyboardInterruptableEvent()

	def get_all(self, _type):
		""" Gets all instances implementing type <_type> """
		if not _type in self.modules:
			raise ValueError, "No such module, %s" % _type
		if not self.insts_implementing.get(_type, None):
			raise ValueError, "No instance implementing %s" % _type
		return self.insts_implementing[_type]

	def get_a(self, _type):
		""" Gets an instance implementing type <_type> """
		tmp = self.get_all(_type)
		ret = pick(tmp)
		if len(tmp) != 1:
			self.l.warn(("get_a: %s all implement %s; "+
				     "picking %s") % (tmp, _type, ret))
		return ret

	def got_a(self, _type):
		""" Returns whether there is an instance implementing <_type>
		"""
		if not _type in self.modules:
			raise ValueError, "No such module, %s" % _type
		return (_type in self.insts_implementing and
			self.insts_implementing[_type])

	class GoCa_Plan(object):
		""" A partial plan for a get_or_create_a call """
		def __init__(self, man, targets, insts=None,
				insts_implementing=None):
			self.man = man
			self.targets = targets
			self.insts = dict() if insts is None else insts
			self.insts_implementing = (dict() if insts_implementing
					is None else insts_implementing)
		def free_instance_name_like(self, name):
			if (not name in self.insts and
					not name in self.man.insts):
				return name
			suffix = 2
			while True:
				shot = "%s-%s" % (name, suffix)
				if (not shot in self.insts and
						not name in self.man.insts):
					return shot
				suffix += 1
		def got_a(self, _type):
			if self.man.got_a(_type):
				return True
			return (_type in self.insts_implementing and
					self.insts_implementing[_type])
		def get_all(self, _type):
			ret = list()
			if self.man.got_a(_type):
				ret.extend(self.man.get_all(_type))
			if _type in self.insts_implementing:
				ret.extend(self.insts_implementing[_type])
			return ret
		def get_a(self, _type):
			return pick(self.get_all(_type))
		@property
		def finished(self):
			return not self.targets
		def plan_a(self, mod):
			name = self.free_instance_name_like(mod)
			self.insts[name] = (name, mod, {})
			md = self.man.modules[mod]
			for mod2 in chain(md.inherits, (mod,)):
				if not mod2 in self.insts_implementing:
					self.insts_implementing[mod2] = list()
				self.insts_implementing[mod2].append(name)
			for depName, dep in md.deps.iteritems():
				if dep.type in self.targets:
					self.targets[dep.type].append(
							(name, depName))
				else:
					self.targets[dep.type] = [
							(name,depName)]
			return name
		def branches(self):
			choices = dict()
			for target in self.targets:
				if self.got_a(target):
					choices[target] = [(True, name) for
						name in self.get_all(target)]
					continue
				choices[target] = [(False, name) for name
					in self.man.modules_implementing[
						target]]
			choices_t = choices.items()
			for choice in product(*[xrange(len(v))
					for k, v in choices_t]):
				plan2 = Manager.GoCa_Plan(self.man, dict(),
						dict(self.insts),
						dict(self.insts_implementing))
				tmp = [(choices_t[n][0], choices_t[n][1][m])
						for n, m in enumerate(choice)]
				for target, inst_or_mod in tmp:
					if inst_or_mod[0]:
						name = inst_or_mod[1]
					else:
						name = plan2.plan_a(
								inst_or_mod[1])
					for _in, depName in \
							self.targets[target]:
						plan2.insts[_in][2][
								depName] = name
				yield plan2
		def execute(self):
			insts = frozenset(self.insts.keys())
			inst_list = tuple(sort_by_successors(insts,
				lambda inst: [self.insts[inst][2][k] for k
				in self.man.modules[self.insts[inst][1]].deps
				if self.insts[inst][2][k] in insts]))
			for name in reversed(inst_list):
				self.man.create_instance(*self.insts[name])

	def get_or_create_a(self, _type):
		""" Gets or creates an instance of type <_type> """
		self.l.debug("get_or_create_a: %s" % _type)
		stack = [Manager.GoCa_Plan(self, {_type:()})]
		while stack:
			p = stack.pop()
			if p.finished:
				p.execute()
				return p.get_a(_type)
			for c in p.branches():
				stack.append(c)
		raise NotImplementedError

	def free_instance_name_like(self, name):
		if not name in self.insts: return name
		suffix = 2
		while True:
			shot = "%s-%s" % (name, suffix)
			if not shot in self.insts:
				return shot
			suffix += 1

	def add_module_definition(self, name, definition):
		if name in self.modules:
			raise ValueError, "Duplicate module name"
		self.modules[name] = definition
		if not definition.implementedBy is None:
			for t in chain(definition.inherits, (name,)):
				if not t in self.modules_implementing:
					self.insts_implementing[t] = set()
					self.modules_implementing[t] = set()
				self.modules_implementing[t].add(name)

	def create_instance(self, name, moduleName, settings):
		""" Creates an instance of <moduleName> at <name> with
		    <settings>. """
		if name in self.insts:
			raise ValueError, \
				"There's already an instance named %s" % \
						name
		if not moduleName in self.modules:
			raise ValueError, \
				"There's no module %s" % moduleName
		md = self.modules[moduleName]
		deps = dict()
		for k, v in md.deps.iteritems():
			if not k in settings:
				settings[k] = self.get_or_create_a(
						v.type)
			if not settings[k] in self.insts:
				raise ValueError, "No such instance %s" \
						% settings[k]
			deps[k] = settings[k]
			settings[k] = self.insts[settings[k]].object
		for k, v in md.vsettings.iteritems():
			if not k in settings:
				settings[k] = v.default
				if v.default is None:
					self.l.warn('%s:%s not set' %
							(name, k))
		self.l.info('create_instance %-15s %s' % (
				name, md.implementedBy))
		cl = get_by_path(md.implementedBy)
		il = logging.getLogger(name)
		obj = cl(settings, il)
		self.register_instance(name, moduleName, obj, settings, deps)

	def register_instance(self, name, moduleName, obj, settings, deps):
		md = self.modules[moduleName]
		self.insts[name] = InstanceInfo(name, moduleName, obj,
						settings, deps)
		for mn in chain(md.inherits, (moduleName,)):
			if not mn in self.insts_implementing:
				self.insts_implementing[mn] = set()
			self.insts_implementing[mn].add(name)
		if md.run:
			self.to_stop.append(name)
			self.daemons.append(name)
		elif hasattr(obj, 'stop'):
			self.to_stop.append(name)

	def run(self):
		def _daemon_entry(ii):
			try:
				ii.object.run()
			except Exception:
				self.l.exception(("Module %s exited "+
						  "abnormally") % ii.name)
				return
			self.l.info("Module %s exited normally" % ii.name)
		assert not self.running
		self.running = True
		# Note that self.daemons is already dependency ordered for us
		for name in self.daemons:
			ii = self.insts[name]
			ii.thread = threading.Thread(target=_daemon_entry,
						     args=[ii])
			ii.thread.start()
		while self.running:
			try:
				self.sleep_event.wait()
			except KeyboardInterrupt:
				self.l.warn("Keyboard interrupt")
				self.running = False
				break
			self.l.info("Woke up")
		self.l.info("Stopping modules")
		for name in reversed(self.to_stop):
			ii = self.insts[name]
			self.l.info("  %s" % ii.name)
			ii.object.stop()
		self.l.info("Joining modules")
		for name in reversed(self.daemons):
			ii = self.insts[name]
			self.l.info("  %s" % ii.name)
			ii.thread.join()

	def change_setting(self, instance_name, key, raw_value):
		""" Change the settings <key> to <raw_value> of an instance
		    named <instance_name>.  <raw_value> should be a string and
		    is properly converted. """
		ii = self.insts[instance_name]
		mo = self.modules[ii.module]
		if key in mo.deps:
			if not raw_value in self.insts:
				raise ValueError, "No such instance %s" % \
						raw_value
			vii = self.insts[raw_value]
			vmo = self.modules[vii.module]
			if not (mo.deps[key].type in vmo.inherits or
					mo.deps[key].type == vii.module):
				raise ValueError, "%s isn't a %s" % (
						raw_value, mo.deps[key].type)
			value = vii.object
		elif key in mo.vsettings:
			value = self.valueTypes[mo.vsettings[key].type](
					raw_value)
		else:
			raise ValueError, "No such settings %s" % key
		self.l.info("Changing %s.%s to %s" % (instance_name,
						      key,
						      raw_value))
		ii.settings[key] = value
		ii.object.change_setting(key, value)

class InstanceInfo(object):
	def __init__(self, name, module, obj, settings, deps):
		self.deps = deps
		self.settings = settings
		self.name = name
		self.object = obj
		self.module = module

class VSettingDefinition(object):
	def __init__(self, _type=None, default=None):
		self.default = default
		self.type = _type

class DepDefinition(object):
	def __init__(self, _type=None):
		self.type = _type

class ModuleDefinition(object):
	def __init__(self, deps=None, vsettings=None, implementedBy=None,
			run=False, inherits=None):
		self.deps = dict() if deps is None else deps
		self.vsettings = dict() if vsettings is None else vsettings
		self.implementedBy = implementedBy
		self.run = run
		self.inherits = list() if inherits is None else inherits

	@property
	def abstract(self):
		return self.implementedBy is None
<commit_msg>sarah.{threading -> _threading}<commit_after>import threading
import logging
import sys
import os

from itertools import product, chain

from sarah.event import Event
from sarah.order import sort_by_successors
from sarah.runtime import get_by_path
from sarah.itertools import pick
from sarah._threading import KeyboardInterruptableEvent

class Module(object):
	def __init__(self, settings, logger):
		for k, v in settings.items():
			setattr(self, k, v)
		self.l = logger
		self.on_settings_changed = dict()

	def change_setting(self, key, value):
		setattr(self, key, value)
		if not key in self.on_settings_changed:
			return
		self.on_settings_changed[key]()

	def register_on_setting_changed(self, key, handler):
		if not key in self.on_settings_changed:
			self.on_settings_changed[key] = Event()
		self.on_settings_changed[key].register(handler)

class Manager(Module):
	def __init__(self, logger=None):
		if logger is None:
			logger = logging.getLogger(object.__repr__(self))
		super(Manager, self).__init__({}, logger)
		self.running = False
		self.modules = dict()
		self.to_stop = list() # objects to stop
		self.daemons = list() # and to join
		self.valueTypes = {'str': str,
				   'float': float,
				   'int': int}
		self.insts = dict()
		# module -> concrete modules implementing module
		self.modules_implementing = dict()
		self.insts_implementing = dict()
		self.add_module_definition('module', ModuleDefinition())
		self.add_module_definition('manager', ModuleDefinition())
		self.register_instance('manager', 'manager', self, {}, {})
		self.sleep_event = KeyboardInterruptableEvent()

	def get_all(self, _type):
		""" Gets all instances implementing type <_type> """
		if not _type in self.modules:
			raise ValueError, "No such module, %s" % _type
		if not self.insts_implementing.get(_type, None):
			raise ValueError, "No instance implementing %s" % _type
		return self.insts_implementing[_type]

	def get_a(self, _type):
		""" Gets an instance implementing type <_type> """
		tmp = self.get_all(_type)
		ret = pick(tmp)
		if len(tmp) != 1:
			self.l.warn(("get_a: %s all implement %s; "+
				     "picking %s") % (tmp, _type, ret))
		return ret

	def got_a(self, _type):
		""" Returns whether there is an instance implementing <_type>
		"""
		if not _type in self.modules:
			raise ValueError, "No such module, %s" % _type
		return (_type in self.insts_implementing and
			self.insts_implementing[_type])

	class GoCa_Plan(object):
		""" A partial plan for a get_or_create_a call """
		def __init__(self, man, targets, insts=None,
				insts_implementing=None):
			self.man = man
			self.targets = targets
			self.insts = dict() if insts is None else insts
			self.insts_implementing = (dict() if insts_implementing
					is None else insts_implementing)
		def free_instance_name_like(self, name):
			if (not name in self.insts and
					not name in self.man.insts):
				return name
			suffix = 2
			while True:
				shot = "%s-%s" % (name, suffix)
				if (not shot in self.insts and
						not name in self.man.insts):
					return shot
				suffix += 1
		def got_a(self, _type):
			if self.man.got_a(_type):
				return True
			return (_type in self.insts_implementing and
					self.insts_implementing[_type])
		def get_all(self, _type):
			ret = list()
			if self.man.got_a(_type):
				ret.extend(self.man.get_all(_type))
			if _type in self.insts_implementing:
				ret.extend(self.insts_implementing[_type])
			return ret
		def get_a(self, _type):
			return pick(self.get_all(_type))
		@property
		def finished(self):
			return not self.targets
		def plan_a(self, mod):
			name = self.free_instance_name_like(mod)
			self.insts[name] = (name, mod, {})
			md = self.man.modules[mod]
			for mod2 in chain(md.inherits, (mod,)):
				if not mod2 in self.insts_implementing:
					self.insts_implementing[mod2] = list()
				self.insts_implementing[mod2].append(name)
			for depName, dep in md.deps.iteritems():
				if dep.type in self.targets:
					self.targets[dep.type].append(
							(name, depName))
				else:
					self.targets[dep.type] = [
							(name,depName)]
			return name
		def branches(self):
			choices = dict()
			for target in self.targets:
				if self.got_a(target):
					choices[target] = [(True, name) for
						name in self.get_all(target)]
					continue
				choices[target] = [(False, name) for name
					in self.man.modules_implementing[
						target]]
			choices_t = choices.items()
			for choice in product(*[xrange(len(v))
					for k, v in choices_t]):
				plan2 = Manager.GoCa_Plan(self.man, dict(),
						dict(self.insts),
						dict(self.insts_implementing))
				tmp = [(choices_t[n][0], choices_t[n][1][m])
						for n, m in enumerate(choice)]
				for target, inst_or_mod in tmp:
					if inst_or_mod[0]:
						name = inst_or_mod[1]
					else:
						name = plan2.plan_a(
								inst_or_mod[1])
					for _in, depName in \
							self.targets[target]:
						plan2.insts[_in][2][
								depName] = name
				yield plan2
		def execute(self):
			insts = frozenset(self.insts.keys())
			inst_list = tuple(sort_by_successors(insts,
				lambda inst: [self.insts[inst][2][k] for k
				in self.man.modules[self.insts[inst][1]].deps
				if self.insts[inst][2][k] in insts]))
			for name in reversed(inst_list):
				self.man.create_instance(*self.insts[name])

	def get_or_create_a(self, _type):
		""" Gets or creates an instance of type <_type> """
		self.l.debug("get_or_create_a: %s" % _type)
		stack = [Manager.GoCa_Plan(self, {_type:()})]
		while stack:
			p = stack.pop()
			if p.finished:
				p.execute()
				return p.get_a(_type)
			for c in p.branches():
				stack.append(c)
		raise NotImplementedError

	def free_instance_name_like(self, name):
		if not name in self.insts: return name
		suffix = 2
		while True:
			shot = "%s-%s" % (name, suffix)
			if not shot in self.insts:
				return shot
			suffix += 1

	def add_module_definition(self, name, definition):
		if name in self.modules:
			raise ValueError, "Duplicate module name"
		self.modules[name] = definition
		if not definition.implementedBy is None:
			for t in chain(definition.inherits, (name,)):
				if not t in self.modules_implementing:
					self.insts_implementing[t] = set()
					self.modules_implementing[t] = set()
				self.modules_implementing[t].add(name)

	def create_instance(self, name, moduleName, settings):
		""" Creates an instance of <moduleName> at <name> with
		    <settings>. """
		if name in self.insts:
			raise ValueError, \
				"There's already an instance named %s" % \
						name
		if not moduleName in self.modules:
			raise ValueError, \
				"There's no module %s" % moduleName
		md = self.modules[moduleName]
		deps = dict()
		for k, v in md.deps.iteritems():
			if not k in settings:
				settings[k] = self.get_or_create_a(
						v.type)
			if not settings[k] in self.insts:
				raise ValueError, "No such instance %s" \
						% settings[k]
			deps[k] = settings[k]
			settings[k] = self.insts[settings[k]].object
		for k, v in md.vsettings.iteritems():
			if not k in settings:
				settings[k] = v.default
				if v.default is None:
					self.l.warn('%s:%s not set' %
							(name, k))
		self.l.info('create_instance %-15s %s' % (
				name, md.implementedBy))
		cl = get_by_path(md.implementedBy)
		il = logging.getLogger(name)
		obj = cl(settings, il)
		self.register_instance(name, moduleName, obj, settings, deps)

	def register_instance(self, name, moduleName, obj, settings, deps):
		md = self.modules[moduleName]
		self.insts[name] = InstanceInfo(name, moduleName, obj,
						settings, deps)
		for mn in chain(md.inherits, (moduleName,)):
			if not mn in self.insts_implementing:
				self.insts_implementing[mn] = set()
			self.insts_implementing[mn].add(name)
		if md.run:
			self.to_stop.append(name)
			self.daemons.append(name)
		elif hasattr(obj, 'stop'):
			self.to_stop.append(name)

	def run(self):
		def _daemon_entry(ii):
			try:
				ii.object.run()
			except Exception:
				self.l.exception(("Module %s exited "+
						  "abnormally") % ii.name)
				return
			self.l.info("Module %s exited normally" % ii.name)
		assert not self.running
		self.running = True
		# Note that self.daemons is already dependency ordered for us
		for name in self.daemons:
			ii = self.insts[name]
			ii.thread = threading.Thread(target=_daemon_entry,
						     args=[ii])
			ii.thread.start()
		while self.running:
			try:
				self.sleep_event.wait()
			except KeyboardInterrupt:
				self.l.warn("Keyboard interrupt")
				self.running = False
				break
			self.l.info("Woke up")
		self.l.info("Stopping modules")
		for name in reversed(self.to_stop):
			ii = self.insts[name]
			self.l.info("  %s" % ii.name)
			ii.object.stop()
		self.l.info("Joining modules")
		for name in reversed(self.daemons):
			ii = self.insts[name]
			self.l.info("  %s" % ii.name)
			ii.thread.join()

	def change_setting(self, instance_name, key, raw_value):
		""" Change the settings <key> to <raw_value> of an instance
		    named <instance_name>.  <raw_value> should be a string and
		    is properly converted. """
		ii = self.insts[instance_name]
		mo = self.modules[ii.module]
		if key in mo.deps:
			if not raw_value in self.insts:
				raise ValueError, "No such instance %s" % \
						raw_value
			vii = self.insts[raw_value]
			vmo = self.modules[vii.module]
			if not (mo.deps[key].type in vmo.inherits or
					mo.deps[key].type == vii.module):
				raise ValueError, "%s isn't a %s" % (
						raw_value, mo.deps[key].type)
			value = vii.object
		elif key in mo.vsettings:
			value = self.valueTypes[mo.vsettings[key].type](
					raw_value)
		else:
			raise ValueError, "No such settings %s" % key
		self.l.info("Changing %s.%s to %s" % (instance_name,
						      key,
						      raw_value))
		ii.settings[key] = value
		ii.object.change_setting(key, value)

class InstanceInfo(object):
	def __init__(self, name, module, obj, settings, deps):
		self.deps = deps
		self.settings = settings
		self.name = name
		self.object = obj
		self.module = module

class VSettingDefinition(object):
	def __init__(self, _type=None, default=None):
		self.default = default
		self.type = _type

class DepDefinition(object):
	def __init__(self, _type=None):
		self.type = _type

class ModuleDefinition(object):
	def __init__(self, deps=None, vsettings=None, implementedBy=None,
			run=False, inherits=None):
		self.deps = dict() if deps is None else deps
		self.vsettings = dict() if vsettings is None else vsettings
		self.implementedBy = implementedBy
		self.run = run
		self.inherits = list() if inherits is None else inherits

	@property
	def abstract(self):
		return self.implementedBy is None
<|endoftext|>


<commit_before># Image manipulation API

from flask import Flask, render_template, send_from_directory, redirect
import os
from PIL import Image

app = Flask(__name__)

APP_ROOT = os.path.dirname(os.path.abspath(__file__))

# default access redirects to API documentation
@app.route("/")
def main():
    return redirect("https://github.com/gxercavins/image-api/blob/master/README.md", code=302)

# rotate filename the specified degrees
@app.route("/rotate/<angle>/<filename>", methods=["GET"])
def rotate(angle, filename):

    # check for valid angle
    angle = int(angle)
    if not -360 < angle < 360:
        return render_template("error.html", message="Invalid angle parameter (-359 to 359)"), 400

    # open and process image
    target = os.path.join(APP_ROOT, 'static/images')
    destination = "/".join([target, filename])

    img = Image.open(destination)
    img = img.rotate(-1*angle)

    # save and return image
    destination = "/".join([target, 'temp.png'])
    if os.path.isfile(destination):
        os.remove(destination)
    img.save(destination)

    return send_image('temp.png')

# flip filename 'vertical' or 'horizontal'
@app.route("/flip/<mode>/<filename>", methods=["GET"])
def flip(mode, filename):

    # open and process image
    target = os.path.join(APP_ROOT, 'static/images')
    destination = "/".join([target, filename])

    img = Image.open(destination)

    # check mode
    if mode == 'horizontal':
        img = img.transpose(Image.FLIP_LEFT_RIGHT)
    elif mode == 'vertical':
        img = img.transpose(Image.FLIP_TOP_BOTTOM)
    else:
        return render_template("error.html", message="Invalid mode (vertical or horizontal)"), 400

    # save and return image
    destination = "/".join([target, 'temp.png'])
    if os.path.isfile(destination):
        os.remove(destination)
    img.save(destination)

    return send_image('temp.png')

# crop filename from (x1,y1) to (x2,y2)
@app.route("/crop/<x1>/<y1>/<x2>/<y2>/<filename>", methods=["GET"])
def crop(x1, y1, x2, y2, filename):

    # open image
    target = os.path.join(APP_ROOT, 'static/images')
    destination = "/".join([target, filename])

    img = Image.open(destination)
    width = img.size[0]
    height = img.size[1]

    # check for valid crop parameters
    x1 = int(x1)
    y1 = int(y1)
    x2 = int(x2)
    y2 = int(y2)

    crop_possible = True
    if not 0 <= x1 < width:
        crop_possible = False
    if not 0 < x2 <= width:
        crop_possible = False
    if not 0 <= y1 < height:
        crop_possible = False
    if not 0 < y2 <= height:
        crop_possible = False
    if not x1 < x2:
        crop_possible = False
    if not y1 < y2:
        crop_possible = False

    # process image
    if crop_possible:
        img = img.crop((x1, y1, x2, y2))
    else:
        return render_template("error.html", message="Crop dimensions not valid"), 400

    # save and return image
    destination = "/".join([target, 'temp.png'])
    if os.path.isfile(destination):
        os.remove(destination)
    img.save(destination)

    return send_image('temp.png')

# blend filename1 and filename2 with alpha parameter
@app.route("/blend/<alpha>/<filename1>/<filename2>", methods=["GET"])
def blend(alpha, filename1, filename2):

    # check for valid alpha
    alpha = float(alpha)
    if not 0 <= alpha <= 100:
        return render_template("error.html", message="Invalid alpha value (0-100)"), 400

    #open images
    target = os.path.join(APP_ROOT, 'static/images')
    destination1 = "/".join([target, filename1])
    destination2 = "/".join([target, filename2])

    img1 = Image.open(destination1)
    img2 = Image.open(destination2)

    # check for dimensions and resize to larger ones
    width = max(img1.size[0], img2.size[0])
    height = max(img1.size[1], img2.size[1])

    img1 = img1.resize((width, height), Image.ANTIALIAS)
    img2 = img2.resize((width, height), Image.ANTIALIAS)

    # if one image in gray scale, convert the other to monochrome
    if len(img1.mode) < 3:
        img2 = img2.convert('L')
    elif len(img2.mode) < 3:
        img1 = img1.convert('L')

    # blend images
    img = Image.blend(img1, img2, float(alpha)/100)

    # save and return
    destination = "/".join([target, 'temp.png'])
    if os.path.isfile(destination):
        os.remove(destination)
    img.save(destination)

    return send_image('temp.png')

# retrieve file from 'static/images' directory
@app.route('/static/images/<filename>')
def send_image(filename):
    return send_from_directory("static/images", filename)

if __name__ == "__main__":
    app.run()<commit_msg>code quality<commit_after># Image manipulation API

from flask import Flask, render_template, send_from_directory, redirect
import os
from PIL import Image

app = Flask(__name__)

APP_ROOT = os.path.dirname(os.path.abspath(__file__))

# default access redirects to API documentation
@app.route("/")
def main():
    return redirect("https://github.com/gxercavins/image-api/blob/master/README.md", code=302)

# rotate filename the specified degrees
@app.route("/rotate/<angle>/<filename>", methods=["GET"])
def rotate(angle, filename):

    # check for valid angle
    angle = int(angle)
    if not -360 < angle < 360:
        return render_template("error.html", message="Invalid angle parameter (-359 to 359)"), 400

    # open and process image
    target = os.path.join(APP_ROOT, 'static/images')
    destination = "/".join([target, filename])

    img = Image.open(destination)
    img = img.rotate(-1*angle)

    # save and return image
    destination = "/".join([target, 'temp.png'])
    if os.path.isfile(destination):
        os.remove(destination)
    img.save(destination)

    return send_image('temp.png')

# flip filename 'vertical' or 'horizontal'
@app.route("/flip/<mode>/<filename>", methods=["GET"])
def flip(mode, filename):

    # open and process image
    target = os.path.join(APP_ROOT, 'static/images')
    destination = "/".join([target, filename])

    img = Image.open(destination)

    # check mode
    if mode == 'horizontal':
        img = img.transpose(Image.FLIP_LEFT_RIGHT)
    elif mode == 'vertical':
        img = img.transpose(Image.FLIP_TOP_BOTTOM)
    else:
        return render_template("error.html", message="Invalid mode (vertical or horizontal)"), 400

    # save and return image
    destination = "/".join([target, 'temp.png'])
    if os.path.isfile(destination):
        os.remove(destination)
    img.save(destination)

    return send_image('temp.png')

# crop filename from (x1,y1) to (x2,y2)
@app.route("/crop/<x1>/<y1>/<x2>/<y2>/<filename>", methods=["GET"])
def crop(x1, y1, x2, y2, filename):

    # open image
    target = os.path.join(APP_ROOT, 'static/images')
    destination = "/".join([target, filename])

    img = Image.open(destination)
    width = img.size[0]
    height = img.size[1]

    # check for valid crop parameters
    [x1, y1, x2, y2] = [int(x1), int(y1), int(x2), int(y2)]

    crop_possible = True
    if not 0 <= x1 < width:
        crop_possible = False
    if not 0 < x2 <= width:
        crop_possible = False
    if not 0 <= y1 < height:
        crop_possible = False
    if not 0 < y2 <= height:
        crop_possible = False
    if not x1 < x2:
        crop_possible = False
    if not y1 < y2:
        crop_possible = False

    # process image
    if crop_possible:
        img = img.crop((x1, y1, x2, y2))
    else:
        return render_template("error.html", message="Crop dimensions not valid"), 400

    # save and return image
    destination = "/".join([target, 'temp.png'])
    if os.path.isfile(destination):
        os.remove(destination)
    img.save(destination)

    return send_image('temp.png')

# blend filename1 and filename2 with alpha parameter
@app.route("/blend/<alpha>/<filename1>/<filename2>", methods=["GET"])
def blend(alpha, filename1, filename2):

    # check for valid alpha
    alpha = float(alpha)
    if not 0 <= alpha <= 100:
        return render_template("error.html", message="Invalid alpha value (0-100)"), 400

    #open images
    target = os.path.join(APP_ROOT, 'static/images')
    destination1 = "/".join([target, filename1])
    destination2 = "/".join([target, filename2])

    img1 = Image.open(destination1)
    img2 = Image.open(destination2)

    # check for dimensions and resize to larger ones
    width = max(img1.size[0], img2.size[0])
    height = max(img1.size[1], img2.size[1])

    img1 = img1.resize((width, height), Image.ANTIALIAS)
    img2 = img2.resize((width, height), Image.ANTIALIAS)

    # if one image in gray scale, convert the other to monochrome
    if len(img1.mode) < 3:
        img2 = img2.convert('L')
    elif len(img2.mode) < 3:
        img1 = img1.convert('L')

    # blend images
    img = Image.blend(img1, img2, float(alpha)/100)

    # save and return
    destination = "/".join([target, 'temp.png'])
    if os.path.isfile(destination):
        os.remove(destination)
    img.save(destination)

    return send_image('temp.png')

# retrieve file from 'static/images' directory
@app.route('/static/images/<filename>')
def send_image(filename):
    return send_from_directory("static/images", filename)

if __name__ == "__main__":
    app.run()
<|endoftext|>