<commit_before>import unittest
import mock

from girder_worker.app import (
    girder_before_task_publish,
    gw_task_prerun,
    gw_task_success,
    gw_task_failure,
    gw_task_postrun)

from contextlib import contextmanager
from girder_worker.utils import JobStatus

@contextmanager
def mock_worker_plugin_utils():
    """girder.plugins is not available unless you are
    working within the context of a rest request. This
    context manager allows us to mock girder.plugins.worker.utils
    without seting up a whole server.
    """

    girder_plugins = mock.MagicMock()
    with mock.patch.dict('sys.modules',
                         **{'girder.plugins': mock.MagicMock(),
                            'girder.plugins.worker': girder_plugins}):
        with mock.patch.object(girder_plugins, 'utils') as utils:
            yield utils

class TestSignals(unittest.TestCase):
    def setUp(self):
        self.headers = {
            'jobInfoSpec': {
                'method': 'PUT',
                'url': 'http://girder:8888/api/v1',
                'reference': 'JOBINFOSPECREFERENCE',
                'headers': {'Girder-Token': 'GIRDERTOKEN'},
                'logPrint': True
            }
        }

    @mock.patch('girder.utility.model_importer.ModelImporter')
    @mock.patch('girder.api.rest.getCurrentUser')
    def test_girder_before_task_publish_with_jobinfospec_no_job_created(self, gcu, mi):

        inputs = dict(sender='test.task',
                      # args, kwargs, options
                      body=[(), {}, {}],
                      headers=self.headers)

        create_job = mi.model.return_value.createJob

        with mock_worker_plugin_utils():
            girder_before_task_publish(**inputs)

        self.assertTrue(not gcu.called)
        self.assertTrue(not create_job.called)

    @mock.patch('girder.utility.model_importer.ModelImporter')
    @mock.patch('girder.api.rest.getCurrentUser')
    def test_girder_before_task_publish_called_with_no_headers_create_job_inputs(self, gcu, mi):

        inputs = dict(sender='test.task',
                      # args, kwargs, options
                      body=[(), {}, {}],
                      headers={'id': 'CELERY-ASYNCRESULT-ID'})

        create_job = mi.model.return_value.createJob

        with mock_worker_plugin_utils():
            girder_before_task_publish(**inputs)

        gcu.assert_called_once()
        create_job.assert_called_once_with(
            **{'title': None,
               'type': None,
               'handler': 'celery_handler',
               'public': False,
               'user': gcu.return_value,
               'args': (),
               'kwargs': {},
               'otherFields': {'celeryTaskId': 'CELERY-ASYNCRESULT-ID'}})

    @mock.patch('girder.utility.model_importer.ModelImporter')
    @mock.patch('girder.api.rest.getCurrentUser')
    def test_girder_before_task_publish_called_with_headers_create_job_inputs(self, gcu, mi):

        inputs = dict(sender='test.task',
                      # args, kwargs, options
                      body=[(), {}, {}],
                      headers={'id': 'CELERY-ASYNCRESULT-ID',
                               'girder_job_title': 'GIRDER_JOB_TITLE',
                               'girder_job_type': 'GIRDER_JOB_TYPE',
                               'girder_job_handler': 'GIRDER_JOB_HANDLER',
                               'girder_job_public': 'GIRDER_JOB_PUBLIC',
                               'girder_job_other_fields': {'SOME_OTHER': 'FIELD'}})

        create_job = mi.model.return_value.createJob

        with mock_worker_plugin_utils():
            girder_before_task_publish(**inputs)

        gcu.assert_called_once()
        create_job.assert_called_once_with(
            **{'title': 'GIRDER_JOB_TITLE',
               'type': 'GIRDER_JOB_TYPE',
               'handler': 'GIRDER_JOB_HANDLER',
               'public': 'GIRDER_JOB_PUBLIC',
               'user': gcu.return_value,
               'args': (),
               'kwargs': {},
               'otherFields': {'celeryTaskId': 'CELERY-ASYNCRESULT-ID',
                               'SOME_OTHER': 'FIELD'}})

    @mock.patch('girder.utility.model_importer.ModelImporter')
    @mock.patch('girder.api.rest.getCurrentUser')
    def test_girder_before_task_publish_jobinfospec_called(self, gcu, mi):

        inputs = dict(sender='test.task',
                      # args, kwargs, options
                      body=[(), {}, {}],
                      headers={'id': 'CELERY-ASYNCRESULT-ID'})

        with mock_worker_plugin_utils() as utils:
            girder_before_task_publish(**inputs)

            utils.jobInfoSpec.asset_called_once()
            utils.getWorkerApiUrl.assert_called_once()

    @mock.patch('girder_worker.utils.JobManager')
    def test_task_prerun(self, jm):
        task = mock.MagicMock()
        task.request.jobInfoSpec = self.headers['jobInfoSpec']
        task.request.parent_id = None

        gw_task_prerun(task=task)

        task.job_manager.updateStatus.assert_called_once_with(
            JobStatus.RUNNING)

    @mock.patch('girder_worker.utils.JobManager')
    def test_task_success(self, jm):
        task = mock.MagicMock()
        task.request.parent_id = None
        task.job_manager = jm(**self.headers)

        gw_task_success(sender=task)

        task.job_manager.updateStatus.assert_called_once_with(
            JobStatus.SUCCESS)

    @mock.patch('girder_worker.utils.JobManager')
    def test_task_failure(self, jm):
        task = mock.MagicMock()
        task.request.parent_id = None
        task.job_manager = jm(**self.headers)

        exc, tb = mock.MagicMock(), mock.MagicMock()
        exc.__str__.return_value = 'MOCKEXCEPTION'

        with mock.patch('girder_worker.app.tb') as traceback:
            traceback.format_tb.return_value = 'TRACEBACK'

            gw_task_failure(sender=task, exception=exc, traceback=tb)

            task.job_manager.write.assert_called_once_with(
                'MagicMock: MOCKEXCEPTION\nTRACEBACK')
            task.job_manager.updateStatus(JobStatus.ERROR)

    @mock.patch('girder_worker.utils.JobManager')
    def test_task_postrun(self, jm):
        task = mock.MagicMock()
        task.request.parent_id = None
        task.job_manager = jm(**self.headers)

        gw_task_postrun(task=task)

        task.job_manager._flush.assert_called_once()
        task.job_manager._redirectPipes.assert_called_once_with(False)
<commit_msg>Mock is_revoked for signal test<commit_after>import unittest
import mock

from girder_worker.app import (
    girder_before_task_publish,
    gw_task_prerun,
    gw_task_success,
    gw_task_failure,
    gw_task_postrun)

from contextlib import contextmanager
from girder_worker.utils import JobStatus

@contextmanager
def mock_worker_plugin_utils():
    """girder.plugins is not available unless you are
    working within the context of a rest request. This
    context manager allows us to mock girder.plugins.worker.utils
    without seting up a whole server.
    """

    girder_plugins = mock.MagicMock()
    with mock.patch.dict('sys.modules',
                         **{'girder.plugins': mock.MagicMock(),
                            'girder.plugins.worker': girder_plugins}):
        with mock.patch.object(girder_plugins, 'utils') as utils:
            yield utils

class TestSignals(unittest.TestCase):
    def setUp(self):
        self.headers = {
            'jobInfoSpec': {
                'method': 'PUT',
                'url': 'http://girder:8888/api/v1',
                'reference': 'JOBINFOSPECREFERENCE',
                'headers': {'Girder-Token': 'GIRDERTOKEN'},
                'logPrint': True
            }
        }

    @mock.patch('girder.utility.model_importer.ModelImporter')
    @mock.patch('girder.api.rest.getCurrentUser')
    def test_girder_before_task_publish_with_jobinfospec_no_job_created(self, gcu, mi):

        inputs = dict(sender='test.task',
                      # args, kwargs, options
                      body=[(), {}, {}],
                      headers=self.headers)

        create_job = mi.model.return_value.createJob

        with mock_worker_plugin_utils():
            girder_before_task_publish(**inputs)

        self.assertTrue(not gcu.called)
        self.assertTrue(not create_job.called)

    @mock.patch('girder.utility.model_importer.ModelImporter')
    @mock.patch('girder.api.rest.getCurrentUser')
    def test_girder_before_task_publish_called_with_no_headers_create_job_inputs(self, gcu, mi):

        inputs = dict(sender='test.task',
                      # args, kwargs, options
                      body=[(), {}, {}],
                      headers={'id': 'CELERY-ASYNCRESULT-ID'})

        create_job = mi.model.return_value.createJob

        with mock_worker_plugin_utils():
            girder_before_task_publish(**inputs)

        gcu.assert_called_once()
        create_job.assert_called_once_with(
            **{'title': None,
               'type': None,
               'handler': 'celery_handler',
               'public': False,
               'user': gcu.return_value,
               'args': (),
               'kwargs': {},
               'otherFields': {'celeryTaskId': 'CELERY-ASYNCRESULT-ID'}})

    @mock.patch('girder.utility.model_importer.ModelImporter')
    @mock.patch('girder.api.rest.getCurrentUser')
    def test_girder_before_task_publish_called_with_headers_create_job_inputs(self, gcu, mi):

        inputs = dict(sender='test.task',
                      # args, kwargs, options
                      body=[(), {}, {}],
                      headers={'id': 'CELERY-ASYNCRESULT-ID',
                               'girder_job_title': 'GIRDER_JOB_TITLE',
                               'girder_job_type': 'GIRDER_JOB_TYPE',
                               'girder_job_handler': 'GIRDER_JOB_HANDLER',
                               'girder_job_public': 'GIRDER_JOB_PUBLIC',
                               'girder_job_other_fields': {'SOME_OTHER': 'FIELD'}})

        create_job = mi.model.return_value.createJob

        with mock_worker_plugin_utils():
            girder_before_task_publish(**inputs)

        gcu.assert_called_once()
        create_job.assert_called_once_with(
            **{'title': 'GIRDER_JOB_TITLE',
               'type': 'GIRDER_JOB_TYPE',
               'handler': 'GIRDER_JOB_HANDLER',
               'public': 'GIRDER_JOB_PUBLIC',
               'user': gcu.return_value,
               'args': (),
               'kwargs': {},
               'otherFields': {'celeryTaskId': 'CELERY-ASYNCRESULT-ID',
                               'SOME_OTHER': 'FIELD'}})

    @mock.patch('girder.utility.model_importer.ModelImporter')
    @mock.patch('girder.api.rest.getCurrentUser')
    def test_girder_before_task_publish_jobinfospec_called(self, gcu, mi):

        inputs = dict(sender='test.task',
                      # args, kwargs, options
                      body=[(), {}, {}],
                      headers={'id': 'CELERY-ASYNCRESULT-ID'})

        with mock_worker_plugin_utils() as utils:
            girder_before_task_publish(**inputs)

            utils.jobInfoSpec.asset_called_once()
            utils.getWorkerApiUrl.assert_called_once()

    @mock.patch('girder_worker.utils.JobManager')
    def test_task_prerun(self, jm):
        task = mock.MagicMock()
        task.request.jobInfoSpec = self.headers['jobInfoSpec']
        task.request.parent_id = None

        gw_task_prerun(task=task)

        task.job_manager.updateStatus.assert_called_once_with(
            JobStatus.RUNNING)

    @mock.patch('girder_worker.app.is_revoked')
    @mock.patch('girder_worker.utils.JobManager')
    def test_task_success(self, jm, is_revoked):
        task = mock.MagicMock()
        task.request.parent_id = None
        task.job_manager = jm(**self.headers)
        is_revoked.return_value = False

        gw_task_success(sender=task)

        task.job_manager.updateStatus.assert_called_once_with(
            JobStatus.SUCCESS)

    @mock.patch('girder_worker.utils.JobManager')
    def test_task_failure(self, jm):
        task = mock.MagicMock()
        task.request.parent_id = None
        task.job_manager = jm(**self.headers)

        exc, tb = mock.MagicMock(), mock.MagicMock()
        exc.__str__.return_value = 'MOCKEXCEPTION'

        with mock.patch('girder_worker.app.tb') as traceback:
            traceback.format_tb.return_value = 'TRACEBACK'

            gw_task_failure(sender=task, exception=exc, traceback=tb)

            task.job_manager.write.assert_called_once_with(
                'MagicMock: MOCKEXCEPTION\nTRACEBACK')
            task.job_manager.updateStatus(JobStatus.ERROR)

    @mock.patch('girder_worker.utils.JobManager')
    def test_task_postrun(self, jm):
        task = mock.MagicMock()
        task.request.parent_id = None
        task.job_manager = jm(**self.headers)

        gw_task_postrun(task=task)

        task.job_manager._flush.assert_called_once()
        task.job_manager._redirectPipes.assert_called_once_with(False)
<|endoftext|>


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

import transit_types
from constants import *
from collections import OrderedDict
from helpers import pairs
import read_handlers as rh
from rolling_cache import RollingCache, is_cacheable, is_cache_key

class Tag(object):
    def __init__(self, tag):
        self.tag = tag

default_options = {"decoders": {"_": rh.NoneHandler,
                                ":": rh.KeywordHandler,
                                "$": rh.SymbolHandler,
                                "?": rh.BooleanHandler,
                                "i": rh.IntHandler,
                                "f": rh.FloatHandler,
                                "u": rh.UuidHandler,
                                "r": rh.UriHandler,
                                "t": rh.DateHandler,
                                "m": rh.DateHandler,
                                "n": rh.BigIntegerHandler,
                                "link": rh.LinkHandler,
                                "list": rh.ListHandler,
                                "set": rh.SetHandler,
                                "cmap": rh.CmapHandler,
                                "'": rh.IdentityHandler},
                   "default_decoder": rh.DefaultHandler}

ground_decoders = {"_": rh.NoneHandler,
                   "?": rh.BooleanHandler,
                   "i": rh.IntHandler,
                   "'": rh.IdentityHandler}

class Decoder(object):
    """ The Decoder is the lowest level entry point for parsing, decoding, and
    fully converting Transit data into Python objects.

    During the creation of a Decoder object, you can specify custom options
    in a dictionary.  One such option is 'decoders'.  Note that while you
    can specify your own decoders and override many of the built in decoders,
    some decoders are silently enforced and cannot be overriden.  These are
    known as Ground Decoders, and are needed to maintain bottom-tier
    compatibility."""

    def __init__(self, options={}):
        self.options = default_options.copy()
        self.options.update(options)

        self.decoders = self.options["decoders"]
        # Always ensure we control the ground decoders
        self.decoders.update(ground_decoders)

    def decode(self, node, cache=None, as_map_key=False):
        """ Given a node of data (any supported decodeable obj - string, dict,
        list), return the decoded object.  Optionally set the current decode
        cache [None].  If None, a new RollingCache is instantiated and used.
        You may also hit to the decoder that this node is to be treated as a
        map key [False].  This is used internally."""
        if not cache:
            cache = RollingCache()
        return self._decode(node, cache, as_map_key)

    def _decode(self, node, cache, as_map_key):
        tp = type(node)
        if tp is unicode:
            return self.decode_string(node, cache, as_map_key)
        elif tp is dict or tp is OrderedDict:
            return self.decode_hash(node, cache, as_map_key)
        elif tp is list:
            return self.decode_list(node, cache, as_map_key)
        elif tp is str:
            return self.decode_string(unicode(node, "utf-8"), cache, as_map_key)
        else:
            return node

    def decode_list(self, node, cache, as_map_key):
        """ Special case decodes map-as-array.
        Otherwise lists are treated as Python lists.

        Arguments follow the same convention as the top-level 'decode'
        function"""
        if node:
            decoded = self._decode(node[0], cache, as_map_key)
            if decoded == MAP_AS_ARR:
                return {self._decode(k, cache, True):
                        self._decode(v, cache, as_map_key)
                        for k,v in pairs(node[1:])}
            elif isinstance(decoded, Tag):
                return self.decode_tag(decoded.tag, self._decode(node[1], cache, as_map_key))
        return tuple(self._decode(x, cache, as_map_key) for x in node)

    def decode_string(self, string, cache, as_map_key):
        """ Decode a string - arguments follow the same convention as the
        top-level 'decode' function"""
        if is_cache_key(string):
            return self.parse_string(cache.decode(string, as_map_key), cache, as_map_key)
        if is_cacheable(string, as_map_key):
            cache.encode(string, as_map_key)
        return self.parse_string(string, cache, as_map_key)

    def decode_tag(self, tag, rep):
        decoder = self.decoders.get(tag, None)
        if decoder:
            return decoder.from_rep(rep)
        else:
            return self.options["default_decoder"].from_rep(tag, rep)

    def decode_hash(self, hash, cache, as_map_key):
        if len(hash) != 1:
            h = {}
            for k, v in hash.items():
                h[self._decode(k, cache, True)] = self._decode(v, cache, False)
            return transit_types.frozendict(h)
        else:
            key,value = hash.items()[0]
            key = self._decode(key, cache, True)
            if isinstance(key, Tag):
                return self.decode_tag(key.tag, self._decode(value, cache, as_map_key))
            else:
                return {key: self._decode(value, cache, False)}

    def parse_string(self, string, cache, as_map_key):
        if string.startswith(ESC):
            m = string[1]
            if m in self.decoders:
                return self.decoders[m].from_rep(string[2:])
            elif m == ESC or m == SUB or m == RES:
                return string[1:]
            elif m == "#":
                return Tag(string[2:])
            else:
                return self.options["default_decoder"].from_rep(string[1], string[2:])
        return string

    def register(self, key_or_tag, obj):
        """ Register a custom Transit tag and new parsing function with the
        decoder.  Also, you can optionally set the 'default_decoder' with
        this function.  Your new tag and parse/decode function will be added
        to the interal dictionary of decoders for this Decoder object"""
        if key_or_tag == "default_decoder":
            self.options["default_decoder"] = obj
        else:
            self.decoders[key_or_tag] = obj

<commit_msg>Fixed bug in order of k/v parse that impacted cache. Still needs performance improvement.<commit_after>## Copyright 2014 Cognitect. All Rights Reserved.
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
##      http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS-IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.

import transit_types
from constants import *
from collections import OrderedDict
from helpers import pairs
import read_handlers as rh
from rolling_cache import RollingCache, is_cacheable, is_cache_key

class Tag(object):
    def __init__(self, tag):
        self.tag = tag

default_options = {"decoders": {"_": rh.NoneHandler,
                                ":": rh.KeywordHandler,
                                "$": rh.SymbolHandler,
                                "?": rh.BooleanHandler,
                                "i": rh.IntHandler,
                                "f": rh.FloatHandler,
                                "u": rh.UuidHandler,
                                "r": rh.UriHandler,
                                "t": rh.DateHandler,
                                "m": rh.DateHandler,
                                "n": rh.BigIntegerHandler,
                                "link": rh.LinkHandler,
                                "list": rh.ListHandler,
                                "set": rh.SetHandler,
                                "cmap": rh.CmapHandler,
                                "'": rh.IdentityHandler},
                   "default_decoder": rh.DefaultHandler}

ground_decoders = {"_": rh.NoneHandler,
                   "?": rh.BooleanHandler,
                   "i": rh.IntHandler,
                   "'": rh.IdentityHandler}

class Decoder(object):
    """ The Decoder is the lowest level entry point for parsing, decoding, and
    fully converting Transit data into Python objects.

    During the creation of a Decoder object, you can specify custom options
    in a dictionary.  One such option is 'decoders'.  Note that while you
    can specify your own decoders and override many of the built in decoders,
    some decoders are silently enforced and cannot be overriden.  These are
    known as Ground Decoders, and are needed to maintain bottom-tier
    compatibility."""

    def __init__(self, options={}):
        self.options = default_options.copy()
        self.options.update(options)

        self.decoders = self.options["decoders"]
        # Always ensure we control the ground decoders
        self.decoders.update(ground_decoders)

    def decode(self, node, cache=None, as_map_key=False):
        """ Given a node of data (any supported decodeable obj - string, dict,
        list), return the decoded object.  Optionally set the current decode
        cache [None].  If None, a new RollingCache is instantiated and used.
        You may also hit to the decoder that this node is to be treated as a
        map key [False].  This is used internally."""
        if not cache:
            cache = RollingCache()
        return self._decode(node, cache, as_map_key)

    def _decode(self, node, cache, as_map_key):
        tp = type(node)
        if tp is unicode:
            return self.decode_string(node, cache, as_map_key)
        elif tp is dict or tp is OrderedDict:
            return self.decode_hash(node, cache, as_map_key)
        elif tp is list:
            return self.decode_list(node, cache, as_map_key)
        elif tp is str:
            return self.decode_string(unicode(node, "utf-8"), cache, as_map_key)
        else:
            return node

    def decode_list(self, node, cache, as_map_key):
        """ Special case decodes map-as-array.
        Otherwise lists are treated as Python lists.

        Arguments follow the same convention as the top-level 'decode'
        function"""
        if node:
            decoded = self._decode(node[0], cache, as_map_key)
            if decoded == MAP_AS_ARR:
                returned_dict = {}
                for k,v in pairs(node[1:]):
                    key = self._decode(k, cache, True)
                    val = self._decode(v, cache, as_map_key)
                    returned_dict[key] = val
                return returned_dict
#                return {self._decode(k, cache, True):
#                        self._decode(v, cache, as_map_key)
#                        for k,v in pairs(node[1:])}
            elif isinstance(decoded, Tag):
                return self.decode_tag(decoded.tag, self._decode(node[1], cache, as_map_key))
        return tuple(self._decode(x, cache, as_map_key) for x in node)

    def decode_string(self, string, cache, as_map_key):
        """ Decode a string - arguments follow the same convention as the
        top-level 'decode' function"""
        if is_cache_key(string):
            return self.parse_string(cache.decode(string, as_map_key), cache, as_map_key)
        if is_cacheable(string, as_map_key):
            cache.encode(string, as_map_key)
        return self.parse_string(string, cache, as_map_key)

    def decode_tag(self, tag, rep):
        decoder = self.decoders.get(tag, None)
        if decoder:
            return decoder.from_rep(rep)
        else:
            return self.options["default_decoder"].from_rep(tag, rep)

    def decode_hash(self, hash, cache, as_map_key):
        if len(hash) != 1:
            h = {}
            for k, v in hash.items():
                h[self._decode(k, cache, True)] = self._decode(v, cache, False)
            return transit_types.frozendict(h)
        else:
            key,value = hash.items()[0]
            key = self._decode(key, cache, True)
            if isinstance(key, Tag):
                return self.decode_tag(key.tag, self._decode(value, cache, as_map_key))
            else:
                return {key: self._decode(value, cache, False)}

    def parse_string(self, string, cache, as_map_key):
        if string.startswith(ESC):
            m = string[1]
            if m in self.decoders:
                return self.decoders[m].from_rep(string[2:])
            elif m == ESC or m == SUB or m == RES:
                return string[1:]
            elif m == "#":
                return Tag(string[2:])
            else:
                return self.options["default_decoder"].from_rep(string[1], string[2:])
        return string

    def register(self, key_or_tag, obj):
        """ Register a custom Transit tag and new parsing function with the
        decoder.  Also, you can optionally set the 'default_decoder' with
        this function.  Your new tag and parse/decode function will be added
        to the interal dictionary of decoders for this Decoder object"""
        if key_or_tag == "default_decoder":
            self.options["default_decoder"] = obj
        else:
            self.decoders[key_or_tag] = obj

<|endoftext|>


<commit_before>from dataactcore.utils import fileF
from tests.unit.dataactcore.factories.fsrs import (FSRSGrantFactory, FSRSProcurementFactory, FSRSSubcontractFactory,
                                                   FSRSSubgrantFactory)
from tests.unit.dataactcore.factories.staging import AwardFinancialAssistanceFactory, AwardProcurementFactory
from tests.unit.dataactcore.factories.job import SubmissionFactory

def test_copy_values_procurement():
    model_row = fileF.ModelRow(None, FSRSProcurementFactory(duns='DUNS'), FSRSSubcontractFactory(duns='DUNS SUB'),
                               None, None)
    mapper = fileF.DeriveValues(procurement='duns')
    assert mapper(model_row) == 'DUNS'
    mapper = fileF.DeriveValues(subcontract='duns')
    assert mapper(model_row) == 'DUNS SUB'
    mapper = fileF.DeriveValues(grant='duns')
    assert mapper(model_row) is None

def test_copy_values_grant():
    model_row = fileF.ModelRow(None, None, None, FSRSGrantFactory(duns='DUNS'), FSRSSubgrantFactory(duns='DUNS SUB'))
    mapper = fileF.DeriveValues(grant='duns')
    assert mapper(model_row) == 'DUNS'
    mapper = fileF.DeriveValues(subgrant='duns')
    assert mapper(model_row) == 'DUNS SUB'
    mapper = fileF.DeriveValues(procurement='duns')
    assert mapper(model_row) is None

def test_country_name():
    model_row = fileF.ModelRow(
        None, None, None, None, FSRSSubgrantFactory(awardee_address_country='USA', principle_place_country='DE')
    )
    entity = fileF.mappings['SubAwardeeLegalEntityCountryName'](model_row)
    assert entity == 'United States'

    place = fileF.mappings['SubAwardeePlaceOfPerformanceCountryName'](model_row)
    assert place == 'Germany'

def test_zipcode_guard():
    model_row = fileF.ModelRow(
        None, None,
        FSRSSubcontractFactory(company_address_country='USA', company_address_zip='12345'),
        None, None
    )
    us_zip = fileF.mappings['SubAwardeeLegalEntityZIP+4'](model_row)
    foreign_zip = fileF.mappings['SubAwardeeLegalEntityForeignPostalCode'](model_row)
    assert us_zip == '12345'
    assert foreign_zip is None

    model_row.subcontract.company_address_country = 'RU'
    us_zip = fileF.mappings['SubAwardeeLegalEntityZIP+4'](model_row)
    foreign_zip = fileF.mappings['SubAwardeeLegalEntityForeignPostalCode'](model_row)
    assert us_zip is None
    assert foreign_zip == '12345'

def test_generate_f_rows(database, monkeypatch):
    """generate_f_rows should find and convert subaward data relevant to a
    specific submission id. We'll compare the resulting DUNs values for
    uniqueness"""
    # Setup - create awards, procurements/grants, subawards
    sess = database.session
    sub_1 = SubmissionFactory()
    sub_2 = SubmissionFactory()
    sess.add_all([sub_1, sub_2])
    sess.commit()

    awards = [AwardProcurementFactory(submission_id=sub_1.submission_id, piid='PIID1', parent_award_id='PIID1',
                                      awarding_sub_tier_agency_c='1234'),
              AwardProcurementFactory(submission_id=sub_1.submission_id, piid='PIID2', parent_award_id='PIID2',
                                      awarding_sub_tier_agency_c='1234'),
              AwardFinancialAssistanceFactory(submission_id=sub_1.submission_id, fain='FAIN1'),
              AwardFinancialAssistanceFactory(submission_id=sub_1.submission_id, fain='FAIN2'),
              AwardProcurementFactory(submission_id=sub_2.submission_id, piid='PIID1', parent_award_id='PIID1',
                                      awarding_sub_tier_agency_c='1234'),
              AwardFinancialAssistanceFactory(submission_id=sub_2.submission_id, fain='FAIN1')]
    sess.add_all(awards)
    procurements = {}
    for piid in ('PIID1', 'PIID2', 'PIID3'):
        procurements[piid] = [
            FSRSProcurementFactory(contract_number=piid, idv_reference_number=piid,
                                   subawards=[FSRSSubcontractFactory() for _ in range(3)],
                                   contracting_office_aid='1234'),
            FSRSProcurementFactory(contract_number=piid, idv_reference_number=piid,
                                   subawards=[],
                                   contracting_office_aid='1234'),
            FSRSProcurementFactory(contract_number=piid, idv_reference_number=piid,
                                   subawards=[FSRSSubcontractFactory() for _ in range(2)],
                                   contracting_office_aid='1234')
        ]
        sess.add_all(procurements[piid])
    grants = {}
    for fain in ('FAIN0', 'FAIN1'):
        grants[fain] = [
            FSRSGrantFactory(fain=fain, subawards=[FSRSSubgrantFactory() for _ in range(3)]),
            FSRSGrantFactory(fain=fain, subawards=[]),
            FSRSGrantFactory(fain=fain, subawards=[FSRSSubgrantFactory() for _ in range(2)])
        ]
        sess.add_all(grants[fain])
    sess.commit()

    actual = {result['SubAwardeeOrRecipientUniqueIdentifier'] for result in fileF.generate_f_rows(sub_1.submission_id)}
    expected = set()
    expected.update(sub.duns for proc in procurements['PIID1'] for sub in proc.subawards)
    expected.update(sub.duns for proc in procurements['PIID2'] for sub in proc.subawards)
    expected.update(sub.duns for grant in grants['FAIN1'] for sub in grant.subawards)
    assert actual == expected

def test_generate_f_rows_naics_desc(database, monkeypatch):
    """The NAICS description should be retireved from an AwardProcurement"""
    sub = SubmissionFactory()
    database.session.add(sub)
    database.session.commit()

    award = AwardProcurementFactory(submission_id=sub.submission_id, awarding_sub_tier_agency_c='1234')
    other_aps = [AwardProcurementFactory(submission_id=award.submission_id, awarding_sub_tier_agency_c='1234')
                 for _ in range(3)]
    proc = FSRSProcurementFactory(contract_number=award.piid, idv_reference_number=award.parent_award_id,
                                  subawards=[FSRSSubcontractFactory(naics=award.naics)],
                                  contracting_office_aid='1234')

    database.session.add_all([award, proc] + other_aps)
    database.session.commit()

    actual = {result['NAICS_Description'] for result in fileF.generate_f_rows(award.submission_id)}
    assert actual == {award.naics_description}
<commit_msg>Including CFDA check in generate_f_rows<commit_after>from dataactcore.utils import fileF
from tests.unit.dataactcore.factories.fsrs import (FSRSGrantFactory, FSRSProcurementFactory, FSRSSubcontractFactory,
                                                   FSRSSubgrantFactory)
from tests.unit.dataactcore.factories.staging import AwardFinancialAssistanceFactory, AwardProcurementFactory
from tests.unit.dataactcore.factories.job import SubmissionFactory

def test_copy_values_procurement():
    model_row = fileF.ModelRow(None, FSRSProcurementFactory(duns='DUNS'), FSRSSubcontractFactory(duns='DUNS SUB'),
                               None, None)
    mapper = fileF.DeriveValues(procurement='duns')
    assert mapper(model_row) == 'DUNS'
    mapper = fileF.DeriveValues(subcontract='duns')
    assert mapper(model_row) == 'DUNS SUB'
    mapper = fileF.DeriveValues(grant='duns')
    assert mapper(model_row) is None

def test_copy_values_grant():
    model_row = fileF.ModelRow(None, None, None, FSRSGrantFactory(duns='DUNS'), FSRSSubgrantFactory(duns='DUNS SUB'))
    mapper = fileF.DeriveValues(grant='duns')
    assert mapper(model_row) == 'DUNS'
    mapper = fileF.DeriveValues(subgrant='duns')
    assert mapper(model_row) == 'DUNS SUB'
    mapper = fileF.DeriveValues(procurement='duns')
    assert mapper(model_row) is None

def test_country_name():
    model_row = fileF.ModelRow(
        None, None, None, None, FSRSSubgrantFactory(awardee_address_country='USA', principle_place_country='DE')
    )
    entity = fileF.mappings['SubAwardeeLegalEntityCountryName'](model_row)
    assert entity == 'United States'

    place = fileF.mappings['SubAwardeePlaceOfPerformanceCountryName'](model_row)
    assert place == 'Germany'

def test_zipcode_guard():
    model_row = fileF.ModelRow(
        None, None,
        FSRSSubcontractFactory(company_address_country='USA', company_address_zip='12345'),
        None, None
    )
    us_zip = fileF.mappings['SubAwardeeLegalEntityZIP+4'](model_row)
    foreign_zip = fileF.mappings['SubAwardeeLegalEntityForeignPostalCode'](model_row)
    assert us_zip == '12345'
    assert foreign_zip is None

    model_row.subcontract.company_address_country = 'RU'
    us_zip = fileF.mappings['SubAwardeeLegalEntityZIP+4'](model_row)
    foreign_zip = fileF.mappings['SubAwardeeLegalEntityForeignPostalCode'](model_row)
    assert us_zip is None
    assert foreign_zip == '12345'

def test_generate_f_rows(database, monkeypatch):
    """generate_f_rows should find and convert subaward data relevant to a
    specific submission id. We'll compare the resulting DUNs values for
    uniqueness"""
    # Setup - create awards, procurements/grants, subawards
    sess = database.session
    sub_1 = SubmissionFactory()
    sub_2 = SubmissionFactory()
    sess.add_all([sub_1, sub_2])
    sess.commit()

    awards = [AwardProcurementFactory(submission_id=sub_1.submission_id, piid='PIID1', parent_award_id='PIID1',
                                      awarding_sub_tier_agency_c='1234'),
              AwardProcurementFactory(submission_id=sub_1.submission_id, piid='PIID2', parent_award_id='PIID2',
                                      awarding_sub_tier_agency_c='1234'),
              AwardFinancialAssistanceFactory(submission_id=sub_1.submission_id, fain='FAIN1'),
              AwardFinancialAssistanceFactory(submission_id=sub_1.submission_id, fain='FAIN2'),
              AwardProcurementFactory(submission_id=sub_2.submission_id, piid='PIID1', parent_award_id='PIID1',
                                      awarding_sub_tier_agency_c='1234'),
              AwardFinancialAssistanceFactory(submission_id=sub_2.submission_id, fain='FAIN1')]
    sess.add_all(awards)
    procurements = {}
    for piid in ('PIID1', 'PIID2', 'PIID3'):
        procurements[piid] = [
            FSRSProcurementFactory(contract_number=piid, idv_reference_number=piid,
                                   subawards=[FSRSSubcontractFactory() for _ in range(3)],
                                   contracting_office_aid='1234'),
            FSRSProcurementFactory(contract_number=piid, idv_reference_number=piid,
                                   subawards=[],
                                   contracting_office_aid='1234'),
            FSRSProcurementFactory(contract_number=piid, idv_reference_number=piid,
                                   subawards=[FSRSSubcontractFactory() for _ in range(2)],
                                   contracting_office_aid='1234')
        ]
        sess.add_all(procurements[piid])
    grants = {}
    cfda_numbers = '49.021 One CFDA; 49.028 Two CFDA'
    for fain in ('FAIN0', 'FAIN1'):
        grants[fain] = [
            FSRSGrantFactory(fain=fain, subawards=[FSRSSubgrantFactory() for _ in range(3)], cfda_numbers=cfda_numbers),
            FSRSGrantFactory(fain=fain, subawards=[]),
            FSRSGrantFactory(fain=fain, subawards=[FSRSSubgrantFactory() for _ in range(2)], cfda_numbers=cfda_numbers)
        ]
        sess.add_all(grants[fain])
    sess.commit()

    actual = {result['SubAwardeeOrRecipientUniqueIdentifier'] for result in fileF.generate_f_rows(sub_1.submission_id)}
    expected = set()
    expected.update(sub.duns for proc in procurements['PIID1'] for sub in proc.subawards)
    expected.update(sub.duns for proc in procurements['PIID2'] for sub in proc.subawards)
    expected.update(sub.duns for grant in grants['FAIN1'] for sub in grant.subawards)
    assert actual == expected

def test_generate_f_rows_naics_desc(database, monkeypatch):
    """The NAICS description should be retireved from an AwardProcurement"""
    sub = SubmissionFactory()
    database.session.add(sub)
    database.session.commit()

    award = AwardProcurementFactory(submission_id=sub.submission_id, awarding_sub_tier_agency_c='1234')
    other_aps = [AwardProcurementFactory(submission_id=award.submission_id, awarding_sub_tier_agency_c='1234')
                 for _ in range(3)]
    proc = FSRSProcurementFactory(contract_number=award.piid, idv_reference_number=award.parent_award_id,
                                  subawards=[FSRSSubcontractFactory(naics=award.naics)],
                                  contracting_office_aid='1234')

    database.session.add_all([award, proc] + other_aps)
    database.session.commit()

    actual = {result['NAICS_Description'] for result in fileF.generate_f_rows(award.submission_id)}
    assert actual == {award.naics_description}
<|endoftext|>


<commit_before>from Constants import constants as const
import math

# Problem A

probe_dist = 133.9 * const.astronomical_unit # AU -> m
probe_v = 17 * 1000  # Km /s -> m/s

# Energy conservation

v_square = (probe_v**2) + (2 * const.sun_mu) * ((1/const.astronomical_unit) - (1/probe_dist))
v = math.sqrt(v_square)

print("Problem A: " + str(v/1000))

# Problem B

jupiter_orbit_r = 5.204 * const.astronomical_unit  # AU -> m
jupiter_m = 1.8987e27  # Kg
jupiter_r= 69911  # Km

# Hohmann transfer

semi_major_a = (const.astronomical_unit + jupiter_orbit_r)/2
V = math.sqrt((2*const.sun_mu/const.astronomical_unit) - (const.sun_mu/semi_major_a))

# v_d = V - V_earth

v_d = V - math.sqrt(const.sun_mu/const.astronomical_unit)

print("Problem B: " + str(v_d/1000))

# Problem B_2

alt = 200 * 1000 # Km -> m
dist = alt + const.earth_r

# escape velocity

v_esc = math.sqrt((2*const.earth_mu)/(dist))

# v_depart^2 = v_dept_infty^2 + v_esc^2

v_depart_sqrt = v_d**2 + v_esc**2
v_depart = math.sqrt(v_depart_sqrt)/1000

print("Problem B_2: " + str(v_depart))

# Problem B_3

# e = v^2/2 - mu/r

e = (v_depart**2)/2

print("Problem B_3: " + str(e))

# Problem B_4

# e = v^2/2 - mu/r
# v = sqrt(2mu/r - mu/a)

v = math.sqrt((2*const.earth_mu/dist) - (const.earth_mu/semi_major_a))

energ = (v**2)/2 - const.sun_mu/jupiter_orbit_r

energ = -const.sun_mu/(2*semi_major_a)

print("Problem B_4: " + str(energ/1000))

# Problem B_5

# using the period

T = math.pi * math.sqrt((semi_major_a**3)/const.sun_mu)
T = T/(60 * 60 * 24)  # s -> days

print("Problem B_5: " + str(T))

# Problem B_6

v_arrival = math.sqrt((2*const.sun_mu/jupiter_orbit_r) - (const.sun_mu/semi_major_a))

v_arrival_excess = v_arrival - math.sqrt(const.sun_mu/jupiter_orbit_r)

print("Problem b_6: " + str(v_arrival_excess/1000))

# Problem B_7

impact = 100000 * 1000  # Km -> m

v_esc = math.sqrt(2 * const.jupiter_mu / impact)
v_esc /= 1000

# vp^2 = v_esc^2 + v_a^2

# Problem C

msl_mass = 4050  # Kg
msl_alt = 200  # Km
orb_vel = 7.78  # Km/s
v_depart = 11.50  # Km/s
isp = 320  # s
m_exp = 5  # Kg/s

# Tsiolkovsky

<commit_msg>Added Unit 4 problems<commit_after>from Constants import constants as const
import math

# Problem A

probe_dist = 133.9 * const.astronomical_unit # AU -> m
probe_v = 17 * 1000  # Km /s -> m/s

# Energy conservation

v_square = (probe_v**2) + (2 * const.sun_mu) * ((1/const.astronomical_unit) - (1/probe_dist))
v = math.sqrt(v_square)

print("Problem A: " + str(v/1000))

# Problem B

jupiter_orbit_r = 5.204 * const.astronomical_unit  # AU -> m
jupiter_m = 1.8987e27  # Kg
jupiter_r= 69911  # Km

# Hohmann transfer

semi_major_a = (const.astronomical_unit + jupiter_orbit_r)/2
V = math.sqrt((2*const.sun_mu/const.astronomical_unit) - (const.sun_mu/semi_major_a))

# v_d = V - V_earth

v_d = V - math.sqrt(const.sun_mu/const.astronomical_unit)

print("Problem B: " + str(v_d/1000))

# Problem B_2

alt = 200 * 1000 # Km -> m
dist = alt + const.earth_r

# escape velocity

v_esc = math.sqrt((2*const.earth_mu)/(dist))

# v_depart^2 = v_dept_infty^2 + v_esc^2

v_depart_sqrt = v_d**2 + v_esc**2
v_depart = math.sqrt(v_depart_sqrt)/1000

print("Problem B_2: " + str(v_depart))

# Problem B_3

# e = v^2/2 - mu/r

e = (v_d**2)/2

print("Problem B_3: " + str(e/(1000*1000)))

# Problem B_4

# e = v^2/2 - mu/r
# v = sqrt(2mu/r - mu/a)

v = math.sqrt((2*const.earth_mu/dist) - (const.earth_mu/semi_major_a))

energ = (v**2)/2 - const.sun_mu/jupiter_orbit_r

energ = -const.sun_mu/(2*semi_major_a)

print("Problem B_4: " + str(energ/(1000*1000)))

# Problem B_5

# using the period

T = math.pi * math.sqrt((semi_major_a**3)/const.sun_mu)
T = T/(60 * 60 * 24)  # s -> days

print("Problem B_5: " + str(T))

# Problem B_6

v_arrival = math.sqrt((2*const.sun_mu/jupiter_orbit_r) - (const.sun_mu/semi_major_a))

v_arrival_excess = v_arrival - math.sqrt(const.sun_mu/jupiter_orbit_r)

print("Problem B_6: " + str(v_arrival_excess/1000))

# Problem B_7

impact = 100000 * 1000  # Km -> m

v_esc = math.sqrt(2 * const.jupiter_mu / impact)
v_esc /= 1000

v_p = math.sqrt(v_esc**2 + v_arrival_excess**2)

print("Problem B_7: " + str(v_p/1000))

# vp^2 = v_esc^2 + v_a^2

# Problem C

msl_mass = 4050  # Kg
msl_alt = 200 * 1000  # Km
orb_vel = 7.78 * 1000  # Km/s
v_depart = 11.50 * 1000 # Km/s
isp = 320  # s
m_exp = 5  # Kg/s

# Tsiolkovsky

# v = g * Isp * log (m / (m - mt))

earth_g = const.earth_mu / (const.earth_r**2)
first_m = earth_g * isp

t = (msl_mass * (1 - math.exp(-orb_vel/first_m))) / m_exp

print("Problem C: " + str(t))

<|endoftext|>


<commit_before>import pickle

import pytest
from praw.models import Subreddit, WikiPage

from ... import UnitTest

class TestSubredit(UnitTest):
    def test_equality(self):
        subreddit1 = Subreddit(self.reddit,
                               _data={'display_name': 'dummy1', 'n': 1})
        subreddit2 = Subreddit(self.reddit,
                               _data={'display_name': 'Dummy1', 'n': 2})
        subreddit3 = Subreddit(self.reddit,
                               _data={'display_name': 'dummy3', 'n': 2})
        assert subreddit1 == subreddit1
        assert subreddit2 == subreddit2
        assert subreddit3 == subreddit3
        assert subreddit1 == subreddit2
        assert subreddit2 != subreddit3
        assert subreddit1 != subreddit3
        assert 'dummy1' == subreddit1
        assert subreddit2 == 'dummy1'

    def test_construct_failure(self):
        message = 'Either `display_name` or `_data` must be provided.'
        with pytest.raises(TypeError) as excinfo:
            Subreddit(self.reddit)
        assert str(excinfo.value) == message

        with pytest.raises(TypeError) as excinfo:
            Subreddit(self.reddit, 'dummy', {'id': 'dummy'})
        assert str(excinfo.value) == message

    def test_fullname(self):
        subreddit = Subreddit(self.reddit, _data={'display_name': 'name',
                                                  'id': 'dummy'})
        assert subreddit.fullname == 't5_dummy'

    def test_hash(self):
        subreddit1 = Subreddit(self.reddit,
                               _data={'display_name': 'dummy1', 'n': 1})
        subreddit2 = Subreddit(self.reddit,
                               _data={'display_name': 'Dummy1', 'n': 2})
        subreddit3 = Subreddit(self.reddit,
                               _data={'display_name': 'dummy3', 'n': 2})
        assert hash(subreddit1) == hash(subreddit1)
        assert hash(subreddit2) == hash(subreddit2)
        assert hash(subreddit3) == hash(subreddit3)
        assert hash(subreddit1) == hash(subreddit2)
        assert hash(subreddit2) != hash(subreddit3)
        assert hash(subreddit1) != hash(subreddit3)

    def test_pickle(self):
        subreddit = Subreddit(self.reddit, _data={'display_name': 'name',
                                                  'id': 'dummy'})
        for level in range(pickle.HIGHEST_PROTOCOL + 1):
            other = pickle.loads(pickle.dumps(subreddit, protocol=level))
            assert subreddit == other

    def test_repr(self):
        subreddit = Subreddit(self.reddit, display_name='name')
        assert repr(subreddit) == 'Subreddit(display_name=\'name\')'

    def test_search__params_not_modified(self):
        params = {'dummy': 'value'}
        subreddit = Subreddit(self.reddit, display_name='name')
        generator = subreddit.search(None, params=params)
        assert generator.params['dummy'] == 'value'
        assert params == {'dummy': 'value'}

    def test_str(self):
        subreddit = Subreddit(self.reddit, _data={'display_name': 'name',
                                                  'id': 'dummy'})
        assert str(subreddit) == 'name'

    def test_submit_failure(self):
        message = 'Either `selftext` or `url` must be provided.'
        subreddit = Subreddit(self.reddit, display_name='name')

        with pytest.raises(TypeError) as excinfo:
            subreddit.submit('Cool title')
        assert str(excinfo.value) == message

        with pytest.raises(TypeError) as excinfo:
            subreddit.submit('Cool title', selftext='a', url='b')
        assert str(excinfo.value) == message

class TestSubredditFlair(UnitTest):
    def test_set__redundant_arguments(self):
        message = '`redditor` must be provided.'
        subreddit = Subreddit(self.reddit, display_name='name')

        with pytest.raises(TypeError) as excinfo:
            subreddit.flair.set()
        assert str(excinfo.value) == message

        with pytest.raises(TypeError) as excinfo:
            subreddit.flair.set('bboe', thing='bboe')
        assert str(excinfo.value) == message

class TestSubreditWiki(UnitTest):
    def test__getitem(self):
        subreddit = Subreddit(self.reddit, display_name='name')
        wikipage = subreddit.wiki['Foo']
        assert isinstance(wikipage, WikiPage)
        assert 'foo' == wikipage.name
<commit_msg>Fixed typo of SubredditWiki unit test<commit_after>import pickle

import pytest
from praw.models import Subreddit, WikiPage

from ... import UnitTest

class TestSubredit(UnitTest):
    def test_equality(self):
        subreddit1 = Subreddit(self.reddit,
                               _data={'display_name': 'dummy1', 'n': 1})
        subreddit2 = Subreddit(self.reddit,
                               _data={'display_name': 'Dummy1', 'n': 2})
        subreddit3 = Subreddit(self.reddit,
                               _data={'display_name': 'dummy3', 'n': 2})
        assert subreddit1 == subreddit1
        assert subreddit2 == subreddit2
        assert subreddit3 == subreddit3
        assert subreddit1 == subreddit2
        assert subreddit2 != subreddit3
        assert subreddit1 != subreddit3
        assert 'dummy1' == subreddit1
        assert subreddit2 == 'dummy1'

    def test_construct_failure(self):
        message = 'Either `display_name` or `_data` must be provided.'
        with pytest.raises(TypeError) as excinfo:
            Subreddit(self.reddit)
        assert str(excinfo.value) == message

        with pytest.raises(TypeError) as excinfo:
            Subreddit(self.reddit, 'dummy', {'id': 'dummy'})
        assert str(excinfo.value) == message

    def test_fullname(self):
        subreddit = Subreddit(self.reddit, _data={'display_name': 'name',
                                                  'id': 'dummy'})
        assert subreddit.fullname == 't5_dummy'

    def test_hash(self):
        subreddit1 = Subreddit(self.reddit,
                               _data={'display_name': 'dummy1', 'n': 1})
        subreddit2 = Subreddit(self.reddit,
                               _data={'display_name': 'Dummy1', 'n': 2})
        subreddit3 = Subreddit(self.reddit,
                               _data={'display_name': 'dummy3', 'n': 2})
        assert hash(subreddit1) == hash(subreddit1)
        assert hash(subreddit2) == hash(subreddit2)
        assert hash(subreddit3) == hash(subreddit3)
        assert hash(subreddit1) == hash(subreddit2)
        assert hash(subreddit2) != hash(subreddit3)
        assert hash(subreddit1) != hash(subreddit3)

    def test_pickle(self):
        subreddit = Subreddit(self.reddit, _data={'display_name': 'name',
                                                  'id': 'dummy'})
        for level in range(pickle.HIGHEST_PROTOCOL + 1):
            other = pickle.loads(pickle.dumps(subreddit, protocol=level))
            assert subreddit == other

    def test_repr(self):
        subreddit = Subreddit(self.reddit, display_name='name')
        assert repr(subreddit) == 'Subreddit(display_name=\'name\')'

    def test_search__params_not_modified(self):
        params = {'dummy': 'value'}
        subreddit = Subreddit(self.reddit, display_name='name')
        generator = subreddit.search(None, params=params)
        assert generator.params['dummy'] == 'value'
        assert params == {'dummy': 'value'}

    def test_str(self):
        subreddit = Subreddit(self.reddit, _data={'display_name': 'name',
                                                  'id': 'dummy'})
        assert str(subreddit) == 'name'

    def test_submit_failure(self):
        message = 'Either `selftext` or `url` must be provided.'
        subreddit = Subreddit(self.reddit, display_name='name')

        with pytest.raises(TypeError) as excinfo:
            subreddit.submit('Cool title')
        assert str(excinfo.value) == message

        with pytest.raises(TypeError) as excinfo:
            subreddit.submit('Cool title', selftext='a', url='b')
        assert str(excinfo.value) == message

class TestSubredditFlair(UnitTest):
    def test_set__redundant_arguments(self):
        message = '`redditor` must be provided.'
        subreddit = Subreddit(self.reddit, display_name='name')

        with pytest.raises(TypeError) as excinfo:
            subreddit.flair.set()
        assert str(excinfo.value) == message

        with pytest.raises(TypeError) as excinfo:
            subreddit.flair.set('bboe', thing='bboe')
        assert str(excinfo.value) == message

class TestSubredditWiki(UnitTest):
    def test__getitem(self):
        subreddit = Subreddit(self.reddit, display_name='name')
        wikipage = subreddit.wiki['Foo']
        assert isinstance(wikipage, WikiPage)
        assert 'foo' == wikipage.name
<|endoftext|>


<commit_before>#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# vulture - Find dead code.
#
# Copyright (c) 2012-2017 Jendrik Seipp (jendrikseipp@gmail.com)
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

from __future__ import print_function

import ast
import codecs
from fnmatch import fnmatchcase
import optparse
import os
import pkgutil
import re
import sys
import tokenize

__version__ = '0.14'

# The ast module in Python 2 trips over "coding" cookies, so strip them.
ENCODING_REGEX = re.compile(
    r"^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+).*?$", flags=re.M)

# Encoding to use when converting input files to unicode.
ENCODING = 'utf-8'

# Parse variable names in template strings.
FORMAT_STRING_PATTERNS = [re.compile(r'\%\((\w+)\)'), re.compile(r'{(\w+)}')]

IGNORED_VARIABLE_NAMES = ['object', 'self']
# True and False are NameConstants since Python 3.4.
if sys.version_info < (3, 4):
    IGNORED_VARIABLE_NAMES += ['True', 'False']

# Ignore star-imported names, since we cannot detect whether they are used.
IGNORED_IMPORTS = ["*"]

def format_path(path):
    if not path:
        return path
    relpath = os.path.relpath(path)
    return relpath if not relpath.startswith('..') else path

class VultureInputException(Exception):
    pass

def read_file(filename):
    # Python >= 3.2
    try:
        # Use encoding detected by tokenize.detect_encoding().
        with tokenize.open(filename) as f:
            return f.read()
    except (SyntaxError, UnicodeDecodeError) as err:
        raise VultureInputException(err)
    except AttributeError:
        # tokenize.open was added in Python 3.2.
        pass

    # Python < 3.2
    try:
        with codecs.open(filename, encoding=ENCODING) as f:
            return f.read()
    except UnicodeDecodeError as err:
        raise VultureInputException(err)

class Item(str):
    def __new__(cls, name, typ, filename, lineno):
        item = str.__new__(cls, name)
        item.typ = typ
        item.filename = filename
        item.lineno = lineno
        return item

class LoggingList(list):
    def __init__(self, name, verbose):
        self._name = name
        self._verbose = verbose
        return list.__init__(self)

    def append(self, item):
        if self._verbose:
            print('{0} <- {1}'.format(self._name, item))
        list.append(self, item)

class Vulture(ast.NodeVisitor):
    """Find dead stuff."""
    def __init__(self, exclude=None, verbose=False):
        self.exclude = []
        for pattern in exclude or []:
            if not any(char in pattern for char in ['*', '?', '[']):
                pattern = '*%s*' % pattern
            self.exclude.append(pattern)

        self.verbose = verbose

        def get_list(name):
            return LoggingList(name, self.verbose)

        self.defined_attrs = get_list('defined_attrs')
        self.defined_funcs = get_list('defined_funcs')
        self.defined_imports = get_list('defined_imports')
        self.defined_props = get_list('defined_props')
        self.defined_vars = get_list('defined_vars')
        self.used_attrs = get_list('used_attrs')
        self.used_vars = get_list('used_vars')
        self.tuple_assign_vars = get_list('tuple_assign_vars')
        self.names_imported_as_aliases = get_list('names_imported_as_aliases')

        self.filename = ''
        self.code = None

    def scan(self, node_string, filename=''):
        node_string = ENCODING_REGEX.sub("", node_string, count=1)
        self.code = node_string.splitlines()
        self.filename = filename
        try:
            node = ast.parse(node_string, filename=self.filename)
        except SyntaxError as err:
            print('%s:%d: %s at "%s"' % (
                format_path(filename), err.lineno, err.msg, err.text.strip()))
        else:
            self.visit(node)

    def _get_modules(self, paths, toplevel=True):
        """Take files from the command line even if they don't end with .py."""
        modules = []
        for path in paths:
            path = os.path.abspath(path)
            if toplevel and path.endswith('.pyc'):
                sys.exit('.pyc files are not supported: {0}'.format(path))
            if os.path.isfile(path) and (path.endswith('.py') or toplevel):
                modules.append(path)
            elif os.path.isdir(path):
                subpaths = [
                    os.path.join(path, filename)
                    for filename in sorted(os.listdir(path))]
                modules.extend(self._get_modules(subpaths, toplevel=False))
            elif toplevel:
                sys.exit('Error: %s could not be found.' % path)
        return modules

    def scavenge(self, paths):
        def exclude(name):
            return any(fnmatchcase(name, pattern) for pattern in self.exclude)

        for module in self._get_modules(paths):
            if exclude(module):
                self.log('Excluded:', module)
                continue

            self.log('Scanning:', module)
            try:
                module_string = read_file(module)
            except VultureInputException as err:
                print('Error: Could not read file %s - %s' % (module, err))
                print('You might want to change the encoding to UTF-8.')
            else:
                self.scan(module_string, filename=module)

            for name in self.defined_imports:
                path = os.path.join('whitelists', name) + '.py'
                if exclude(path):
                    self.log('Excluded whitelist:', path)
                else:
                    try:
                        module_data = pkgutil.get_data('vulture', path)
                        self.log('Included whitelist:', path)
                    except IOError:
                        # Most imported modules don't have a whitelist.
                        continue
                    if module_data is None:
                        sys.exit('Error: Please use "python -m vulture".')
                    module_string = module_data.decode("utf-8")
                    self.scan(module_string, filename=path)

    def report(self):
        def file_lineno(item):
            return (item.filename.lower(), item.lineno)
        unused_item_found = False
        for item in sorted(
                self.unused_funcs + self.unused_imports + self.unused_props +
                self.unused_vars + self.unused_attrs, key=file_lineno):
            print("%s:%d: Unused %s '%s'" % (
                format_path(item.filename), item.lineno, item.typ, item))
            unused_item_found = True
        return unused_item_found

    def get_unused(self, defined, used):
        return list(sorted(set(defined) - set(used), key=lambda x: x.lower()))

    @property
    def unused_funcs(self):
        return self.get_unused(
            self.defined_funcs,
            self.used_attrs + self.used_vars + self.names_imported_as_aliases)

    @property
    def unused_imports(self):
        return self.get_unused(
            self.defined_imports, self.used_vars + IGNORED_IMPORTS)

    @property
    def unused_props(self):
        return self.get_unused(self.defined_props, self.used_attrs)

    @property
    def unused_vars(self):
        return self.get_unused(
            self.defined_vars,
            self.used_attrs + self.used_vars + self.tuple_assign_vars +
            self.names_imported_as_aliases)

    @property
    def unused_attrs(self):
        return self.get_unused(
            self.defined_attrs,
            self.used_attrs + self.used_vars)

    def _define_variable(self, name, lineno):
        # Ignore _, _x (pylint convention), __x, __x__ (special method).
        if name not in IGNORED_VARIABLE_NAMES and not name.startswith('_'):
            self.defined_vars.append(
                Item(name, 'variable', self.filename, lineno))

    def _get_lineno(self, node):
        return getattr(node, 'lineno', 1)

    def _get_line(self, node):
        return self.code[self._get_lineno(node) - 1] if self.code else ''

    def _get_item(self, node, typ):
        name = getattr(node, 'name', None)
        id_ = getattr(node, 'id', None)
        attr = getattr(node, 'attr', None)
        assert bool(name) ^ bool(id_) ^ bool(attr), (typ, dir(node))
        return Item(name or id_ or attr, typ, self.filename, node.lineno)

    def _ignore_function(self, name):
        ignore = (
            (name.startswith('__') and name.endswith('__')) or
            (os.path.basename(self.filename).startswith('test_') and
                name.startswith(('test_', 'Test'))))
        if ignore:
            self.log(
                'Ignoring class or function {0} due to its name'.format(name))
        return ignore

    def log(self, *args):
        if self.verbose:
            print(*args)

    def print_node(self, node):
        # Only create the strings if we'll also print them.
        if self.verbose:
            self.log(
                self._get_lineno(node), ast.dump(node), self._get_line(node))

    def visit_arg(self, node):
        """Function argument. Python 3 only. Has lineno since Python 3.4"""
        self._define_variable(node.arg, getattr(node, 'lineno', -1))

    def visit_FunctionDef(self, node):
        for decorator in node.decorator_list:
            if getattr(decorator, 'id', None) == 'property':
                self.defined_props.append(self._get_item(node, 'property'))
                break
        else:
            # Function is not a property.
            if not self._ignore_function(node.name):
                self.defined_funcs.append(self._get_item(node, 'function'))

        # Detect *args and **kwargs parameters. Python 3 recognizes them
        # in visit_Name. For Python 2 we use this workaround. We can't
        # use visit_arguments, because its node has no lineno.
        for param in [node.args.vararg, node.args.kwarg]:
            if param and isinstance(param, str):
                self._define_variable(param, node.lineno)

    def visit_Attribute(self, node):
        item = self._get_item(node, 'attribute')
        if isinstance(node.ctx, ast.Store):
            self.defined_attrs.append(item)
        elif isinstance(node.ctx, ast.Load):
            self.used_attrs.append(item)

    def visit_Name(self, node):
        if (isinstance(node.ctx, ast.Load) and
                node.id not in IGNORED_VARIABLE_NAMES):
            self.used_vars.append(node.id)
        elif isinstance(node.ctx, (ast.Param, ast.Store)):
            self._define_variable(node.id, node.lineno)

    def visit_alias(self, node):
        """
        Use the methods below for imports to have access to line numbers
        and to filter imports from __future__.
        """
        pass

    def visit_Import(self, node):
        self._add_aliases(node)

    def visit_ImportFrom(self, node):
        if node.module != '__future__':
            self._add_aliases(node)

    def _add_aliases(self, node):
        assert isinstance(node, (ast.Import, ast.ImportFrom))
        for name_and_alias in node.names:
            # Store only top-level module name ("os.path" -> "os").
            # We can't detect when "os.path" is used.
            name = name_and_alias.name.partition('.')[0]
            alias = name_and_alias.asname
            self.defined_imports.append(
                Item(alias or name, 'import', self.filename, node.lineno))
            if alias is not None:
                self.names_imported_as_aliases.append(name_and_alias.name)

    def _find_tuple_assigns(self, node):
        # Find all tuple assignments. Those have the form
        # Assign->Tuple->Name or For->Tuple->Name or comprehension->Tuple->Name
        for child in ast.iter_child_nodes(node):
            if not isinstance(child, ast.Tuple):
                continue
            for grandchild in ast.walk(child):
                if (isinstance(grandchild, ast.Name) and
                        isinstance(grandchild.ctx, ast.Store)):
                    self.tuple_assign_vars.append(grandchild.id)

    def visit_Assign(self, node):
        self._find_tuple_assigns(node)

    def visit_For(self, node):
        self._find_tuple_assigns(node)

    def visit_comprehension(self, node):
        self._find_tuple_assigns(node)

    def visit_ClassDef(self, node):
        if not self._ignore_function(node.name):
            self.defined_funcs.append(self._get_item(node, 'class'))

    def visit_Str(self, node):
        """
        Variables may appear in format strings:

        '%(my_var)s' % locals()
        '{my_var}'.format(**locals())

        """
        for pattern in FORMAT_STRING_PATTERNS:
            self.used_vars.extend(pattern.findall(node.s))

    def visit(self, node):
        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, None)
        self.print_node(node)
        if visitor:
            visitor(node)
        else:
            self.log('Unhandled')
        return self.generic_visit(node)

def parse_args():
    def csv(option, _, value, parser):
        setattr(parser.values, option.dest, value.split(','))
    usage = """\
usage: %prog [options] PATH [PATH ...]

Paths may be Python files or directories. For each directory vulture
analyzes all contained *.py files.
"""
    version = "vulture {0}".format(__version__)
    parser = optparse.OptionParser(usage=usage, version=version)
    parser.add_option(
        '--exclude', action='callback', callback=csv,
        type='string', default=[],
        help='Comma-separated list of paths to ignore (e.g. .svn,external)')
    parser.add_option('-v', '--verbose', action='store_true')
    options, args = parser.parse_args()
    return options, args

def main():
    options, args = parse_args()
    vulture = Vulture(exclude=options.exclude, verbose=options.verbose)
    vulture.scavenge(args)
    sys.exit(vulture.report())

if __name__ == '__main__':
    main()
<commit_msg>Update version number to 0.15 for release.<commit_after>#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# vulture - Find dead code.
#
# Copyright (c) 2012-2017 Jendrik Seipp (jendrikseipp@gmail.com)
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

from __future__ import print_function

import ast
import codecs
from fnmatch import fnmatchcase
import optparse
import os
import pkgutil
import re
import sys
import tokenize

__version__ = '0.15'

# The ast module in Python 2 trips over "coding" cookies, so strip them.
ENCODING_REGEX = re.compile(
    r"^[ \t\v]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+).*?$", flags=re.M)

# Encoding to use when converting input files to unicode.
ENCODING = 'utf-8'

# Parse variable names in template strings.
FORMAT_STRING_PATTERNS = [re.compile(r'\%\((\w+)\)'), re.compile(r'{(\w+)}')]

IGNORED_VARIABLE_NAMES = ['object', 'self']
# True and False are NameConstants since Python 3.4.
if sys.version_info < (3, 4):
    IGNORED_VARIABLE_NAMES += ['True', 'False']

# Ignore star-imported names, since we cannot detect whether they are used.
IGNORED_IMPORTS = ["*"]

def format_path(path):
    if not path:
        return path
    relpath = os.path.relpath(path)
    return relpath if not relpath.startswith('..') else path

class VultureInputException(Exception):
    pass

def read_file(filename):
    # Python >= 3.2
    try:
        # Use encoding detected by tokenize.detect_encoding().
        with tokenize.open(filename) as f:
            return f.read()
    except (SyntaxError, UnicodeDecodeError) as err:
        raise VultureInputException(err)
    except AttributeError:
        # tokenize.open was added in Python 3.2.
        pass

    # Python < 3.2
    try:
        with codecs.open(filename, encoding=ENCODING) as f:
            return f.read()
    except UnicodeDecodeError as err:
        raise VultureInputException(err)

class Item(str):
    def __new__(cls, name, typ, filename, lineno):
        item = str.__new__(cls, name)
        item.typ = typ
        item.filename = filename
        item.lineno = lineno
        return item

class LoggingList(list):
    def __init__(self, name, verbose):
        self._name = name
        self._verbose = verbose
        return list.__init__(self)

    def append(self, item):
        if self._verbose:
            print('{0} <- {1}'.format(self._name, item))
        list.append(self, item)

class Vulture(ast.NodeVisitor):
    """Find dead stuff."""
    def __init__(self, exclude=None, verbose=False):
        self.exclude = []
        for pattern in exclude or []:
            if not any(char in pattern for char in ['*', '?', '[']):
                pattern = '*%s*' % pattern
            self.exclude.append(pattern)

        self.verbose = verbose

        def get_list(name):
            return LoggingList(name, self.verbose)

        self.defined_attrs = get_list('defined_attrs')
        self.defined_funcs = get_list('defined_funcs')
        self.defined_imports = get_list('defined_imports')
        self.defined_props = get_list('defined_props')
        self.defined_vars = get_list('defined_vars')
        self.used_attrs = get_list('used_attrs')
        self.used_vars = get_list('used_vars')
        self.tuple_assign_vars = get_list('tuple_assign_vars')
        self.names_imported_as_aliases = get_list('names_imported_as_aliases')

        self.filename = ''
        self.code = None

    def scan(self, node_string, filename=''):
        node_string = ENCODING_REGEX.sub("", node_string, count=1)
        self.code = node_string.splitlines()
        self.filename = filename
        try:
            node = ast.parse(node_string, filename=self.filename)
        except SyntaxError as err:
            print('%s:%d: %s at "%s"' % (
                format_path(filename), err.lineno, err.msg, err.text.strip()))
        else:
            self.visit(node)

    def _get_modules(self, paths, toplevel=True):
        """Take files from the command line even if they don't end with .py."""
        modules = []
        for path in paths:
            path = os.path.abspath(path)
            if toplevel and path.endswith('.pyc'):
                sys.exit('.pyc files are not supported: {0}'.format(path))
            if os.path.isfile(path) and (path.endswith('.py') or toplevel):
                modules.append(path)
            elif os.path.isdir(path):
                subpaths = [
                    os.path.join(path, filename)
                    for filename in sorted(os.listdir(path))]
                modules.extend(self._get_modules(subpaths, toplevel=False))
            elif toplevel:
                sys.exit('Error: %s could not be found.' % path)
        return modules

    def scavenge(self, paths):
        def exclude(name):
            return any(fnmatchcase(name, pattern) for pattern in self.exclude)

        for module in self._get_modules(paths):
            if exclude(module):
                self.log('Excluded:', module)
                continue

            self.log('Scanning:', module)
            try:
                module_string = read_file(module)
            except VultureInputException as err:
                print('Error: Could not read file %s - %s' % (module, err))
                print('You might want to change the encoding to UTF-8.')
            else:
                self.scan(module_string, filename=module)

            for name in self.defined_imports:
                path = os.path.join('whitelists', name) + '.py'
                if exclude(path):
                    self.log('Excluded whitelist:', path)
                else:
                    try:
                        module_data = pkgutil.get_data('vulture', path)
                        self.log('Included whitelist:', path)
                    except IOError:
                        # Most imported modules don't have a whitelist.
                        continue
                    if module_data is None:
                        sys.exit('Error: Please use "python -m vulture".')
                    module_string = module_data.decode("utf-8")
                    self.scan(module_string, filename=path)

    def report(self):
        def file_lineno(item):
            return (item.filename.lower(), item.lineno)
        unused_item_found = False
        for item in sorted(
                self.unused_funcs + self.unused_imports + self.unused_props +
                self.unused_vars + self.unused_attrs, key=file_lineno):
            print("%s:%d: Unused %s '%s'" % (
                format_path(item.filename), item.lineno, item.typ, item))
            unused_item_found = True
        return unused_item_found

    def get_unused(self, defined, used):
        return list(sorted(set(defined) - set(used), key=lambda x: x.lower()))

    @property
    def unused_funcs(self):
        return self.get_unused(
            self.defined_funcs,
            self.used_attrs + self.used_vars + self.names_imported_as_aliases)

    @property
    def unused_imports(self):
        return self.get_unused(
            self.defined_imports, self.used_vars + IGNORED_IMPORTS)

    @property
    def unused_props(self):
        return self.get_unused(self.defined_props, self.used_attrs)

    @property
    def unused_vars(self):
        return self.get_unused(
            self.defined_vars,
            self.used_attrs + self.used_vars + self.tuple_assign_vars +
            self.names_imported_as_aliases)

    @property
    def unused_attrs(self):
        return self.get_unused(
            self.defined_attrs,
            self.used_attrs + self.used_vars)

    def _define_variable(self, name, lineno):
        # Ignore _, _x (pylint convention), __x, __x__ (special method).
        if name not in IGNORED_VARIABLE_NAMES and not name.startswith('_'):
            self.defined_vars.append(
                Item(name, 'variable', self.filename, lineno))

    def _get_lineno(self, node):
        return getattr(node, 'lineno', 1)

    def _get_line(self, node):
        return self.code[self._get_lineno(node) - 1] if self.code else ''

    def _get_item(self, node, typ):
        name = getattr(node, 'name', None)
        id_ = getattr(node, 'id', None)
        attr = getattr(node, 'attr', None)
        assert bool(name) ^ bool(id_) ^ bool(attr), (typ, dir(node))
        return Item(name or id_ or attr, typ, self.filename, node.lineno)

    def _ignore_function(self, name):
        ignore = (
            (name.startswith('__') and name.endswith('__')) or
            (os.path.basename(self.filename).startswith('test_') and
                name.startswith(('test_', 'Test'))))
        if ignore:
            self.log(
                'Ignoring class or function {0} due to its name'.format(name))
        return ignore

    def log(self, *args):
        if self.verbose:
            print(*args)

    def print_node(self, node):
        # Only create the strings if we'll also print them.
        if self.verbose:
            self.log(
                self._get_lineno(node), ast.dump(node), self._get_line(node))

    def visit_arg(self, node):
        """Function argument. Python 3 only. Has lineno since Python 3.4"""
        self._define_variable(node.arg, getattr(node, 'lineno', -1))

    def visit_FunctionDef(self, node):
        for decorator in node.decorator_list:
            if getattr(decorator, 'id', None) == 'property':
                self.defined_props.append(self._get_item(node, 'property'))
                break
        else:
            # Function is not a property.
            if not self._ignore_function(node.name):
                self.defined_funcs.append(self._get_item(node, 'function'))

        # Detect *args and **kwargs parameters. Python 3 recognizes them
        # in visit_Name. For Python 2 we use this workaround. We can't
        # use visit_arguments, because its node has no lineno.
        for param in [node.args.vararg, node.args.kwarg]:
            if param and isinstance(param, str):
                self._define_variable(param, node.lineno)

    def visit_Attribute(self, node):
        item = self._get_item(node, 'attribute')
        if isinstance(node.ctx, ast.Store):
            self.defined_attrs.append(item)
        elif isinstance(node.ctx, ast.Load):
            self.used_attrs.append(item)

    def visit_Name(self, node):
        if (isinstance(node.ctx, ast.Load) and
                node.id not in IGNORED_VARIABLE_NAMES):
            self.used_vars.append(node.id)
        elif isinstance(node.ctx, (ast.Param, ast.Store)):
            self._define_variable(node.id, node.lineno)

    def visit_alias(self, node):
        """
        Use the methods below for imports to have access to line numbers
        and to filter imports from __future__.
        """
        pass

    def visit_Import(self, node):
        self._add_aliases(node)

    def visit_ImportFrom(self, node):
        if node.module != '__future__':
            self._add_aliases(node)

    def _add_aliases(self, node):
        assert isinstance(node, (ast.Import, ast.ImportFrom))
        for name_and_alias in node.names:
            # Store only top-level module name ("os.path" -> "os").
            # We can't detect when "os.path" is used.
            name = name_and_alias.name.partition('.')[0]
            alias = name_and_alias.asname
            self.defined_imports.append(
                Item(alias or name, 'import', self.filename, node.lineno))
            if alias is not None:
                self.names_imported_as_aliases.append(name_and_alias.name)

    def _find_tuple_assigns(self, node):
        # Find all tuple assignments. Those have the form
        # Assign->Tuple->Name or For->Tuple->Name or comprehension->Tuple->Name
        for child in ast.iter_child_nodes(node):
            if not isinstance(child, ast.Tuple):
                continue
            for grandchild in ast.walk(child):
                if (isinstance(grandchild, ast.Name) and
                        isinstance(grandchild.ctx, ast.Store)):
                    self.tuple_assign_vars.append(grandchild.id)

    def visit_Assign(self, node):
        self._find_tuple_assigns(node)

    def visit_For(self, node):
        self._find_tuple_assigns(node)

    def visit_comprehension(self, node):
        self._find_tuple_assigns(node)

    def visit_ClassDef(self, node):
        if not self._ignore_function(node.name):
            self.defined_funcs.append(self._get_item(node, 'class'))

    def visit_Str(self, node):
        """
        Variables may appear in format strings:

        '%(my_var)s' % locals()
        '{my_var}'.format(**locals())

        """
        for pattern in FORMAT_STRING_PATTERNS:
            self.used_vars.extend(pattern.findall(node.s))

    def visit(self, node):
        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, None)
        self.print_node(node)
        if visitor:
            visitor(node)
        else:
            self.log('Unhandled')
        return self.generic_visit(node)

def parse_args():
    def csv(option, _, value, parser):
        setattr(parser.values, option.dest, value.split(','))
    usage = """\
usage: %prog [options] PATH [PATH ...]

Paths may be Python files or directories. For each directory vulture
analyzes all contained *.py files.
"""
    version = "vulture {0}".format(__version__)
    parser = optparse.OptionParser(usage=usage, version=version)
    parser.add_option(
        '--exclude', action='callback', callback=csv,
        type='string', default=[],
        help='Comma-separated list of paths to ignore (e.g. .svn,external)')
    parser.add_option('-v', '--verbose', action='store_true')
    options, args = parser.parse_args()
    return options, args

def main():
    options, args = parse_args()
    vulture = Vulture(exclude=options.exclude, verbose=options.verbose)
    vulture.scavenge(args)
    sys.exit(vulture.report())

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