diff --git a/docs/source/whatsnew/0.8.5.txt b/docs/source/whatsnew/0.8.5.txt index 738483ea..f1add731 100644 --- a/docs/source/whatsnew/0.8.5.txt +++ b/docs/source/whatsnew/0.8.5.txt @@ -16,20 +16,20 @@ None Enhancements ~~~~~~~~~~~~ -* Added new datasets - :class:`~zipline.pipeline.data.buyback_auth.CashBuybackAuthorizations` +* Added new datasets + :class:`~zipline.pipeline.data.buyback_auth.CashBuybackAuthorizations` and :class:`~zipline.pipeline.data.buyback_auth.ShareBuybackAuthorizations` for use in the Pipeline API. These datasets provide an abstract interface for - adding cash and share buyback authorizations data, respectively, to a new - algorithm. pandas-based reference implementations for these datasets can be found in - :mod:`zipline.pipeline.loaders.buyback_auth`, and experimental blaze-based - implementations can be found in + adding cash and share buyback authorizations data, respectively, to a new + algorithm. pandas-based reference implementations for these datasets can be + found in :mod:`zipline.pipeline.loaders.buyback_auth`, and experimental + blaze-based implementations can be found in :mod:`zipline.pipeline.loaders.blaze.buyback_auth`. (:issue:`1022`). * Added new built-in factors, :class:`zipline.pipeline.factors.BusinessDaysSinceCashBuybackAuth` and :class:`zipline.pipeline.factors.BusinessDaysSinceShareBuybackAuth`. These - factors use the new ``CashBuybackAuthorizations`` and + factors use the new ``CashBuybackAuthorizations`` and ``ShareBuybackAuthorizations`` datasets, respectively. (:issue:`1022`). @@ -70,4 +70,8 @@ None Miscellaneous ~~~~~~~~~~~~~ -None +* Adds :class:`~zipline.testing.fixtures.ZiplineTestCase` which provides hooks + to consume test fixtures. Fixtures are things like: + :class:`~zipline.testing.fixtures.WithAssetFinder` which will make + ``self.asset_finder`` available to your test with some mock data + (:issue:`1042`). diff --git a/tests/data/test_us_equity_pricing.py b/tests/data/test_us_equity_pricing.py index e68cc2ea..c5abfa90 100644 --- a/tests/data/test_us_equity_pricing.py +++ b/tests/data/test_us_equity_pricing.py @@ -39,9 +39,7 @@ from zipline.data.us_equity_pricing import ( ) from zipline.finance.trading import TradingEnvironment from zipline.pipeline.data import USEquityPricing -from zipline.utils.test_utils import ( - seconds_to_timestamp, -) +from zipline.testing import seconds_to_timestamp TEST_CALENDAR_START = Timestamp('2015-06-01', tz='UTC') TEST_CALENDAR_STOP = Timestamp('2015-06-30', tz='UTC') diff --git a/tests/history_cases.py b/tests/history_cases.py index f0ad8b0f..8fa94b11 100644 --- a/tests/history_cases.py +++ b/tests/history_cases.py @@ -8,7 +8,7 @@ import numpy as np from zipline.finance.trading import TradingEnvironment, noop_load from zipline.history.history import HistorySpec from zipline.protocol import BarData -from zipline.utils.test_utils import to_utc +from zipline.testing import to_utc _cases_env = TradingEnvironment(load=noop_load) diff --git a/tests/pipeline/base.py b/tests/pipeline/base.py index 8725d104..8da70e5f 100644 --- a/tests/pipeline/base.py +++ b/tests/pipeline/base.py @@ -16,18 +16,19 @@ from zipline.pipeline import Pipeline from zipline.pipeline.common import TS_FIELD_NAME from zipline.pipeline.engine import SimplePipelineEngine from zipline.pipeline.term import AssetExists -from zipline.utils.numpy_utils import ( - NaTD, - make_datetime64D -) -from zipline.utils.pandas_utils import explode -from zipline.utils.test_utils import ( +from zipline.testing import ( ExplodingObject, gen_calendars, make_simple_equity_info, num_days_in_range, tmp_asset_finder, ) + +from zipline.utils.numpy_utils import ( + NaTD, + make_datetime64D +) +from zipline.utils.pandas_utils import explode from zipline.utils.tradingcalendar import trading_day diff --git a/tests/pipeline/test_adjusted_array.py b/tests/pipeline/test_adjusted_array.py index f7be0b72..a5fab747 100644 --- a/tests/pipeline/test_adjusted_array.py +++ b/tests/pipeline/test_adjusted_array.py @@ -22,6 +22,7 @@ from zipline.lib.adjustment import ( Float64Overwrite, ) from zipline.lib.adjusted_array import AdjustedArray, NOMASK +from zipline.testing import check_arrays, parameter_space from zipline.utils.numpy_utils import ( coerce_to_dtype, datetime64ns_dtype, @@ -29,7 +30,6 @@ from zipline.utils.numpy_utils import ( float64_dtype, int64_dtype, ) -from zipline.utils.test_utils import check_arrays, parameter_space def moving_window(array, nrows): diff --git a/tests/pipeline/test_blaze.py b/tests/pipeline/test_blaze.py index 12016c9a..652b3793 100644 --- a/tests/pipeline/test_blaze.py +++ b/tests/pipeline/test_blaze.py @@ -38,7 +38,7 @@ from zipline.utils.numpy_utils import ( int64_dtype, repeat_last_axis, ) -from zipline.utils.test_utils import ( +from zipline.testing import ( tmp_asset_finder, make_simple_equity_info, ) diff --git a/tests/pipeline/test_buyback_auth.py b/tests/pipeline/test_buyback_auth.py index 818044e0..f97aa049 100644 --- a/tests/pipeline/test_buyback_auth.py +++ b/tests/pipeline/test_buyback_auth.py @@ -37,9 +37,7 @@ from zipline.pipeline.loaders.blaze import ( BlazeCashBuybackAuthorizationsLoader, BlazeShareBuybackAuthorizationsLoader, ) -from zipline.utils.test_utils import ( - tmp_asset_finder, -) +from zipline.testing import tmp_asset_finder from .base import EventLoaderCommonMixin, DATE_FIELD_NAME diff --git a/tests/pipeline/test_column.py b/tests/pipeline/test_column.py index 3cf08b48..d270dc3b 100644 --- a/tests/pipeline/test_column.py +++ b/tests/pipeline/test_column.py @@ -9,7 +9,7 @@ from pandas.util.testing import assert_frame_equal from zipline.pipeline import Pipeline from zipline.pipeline.data.testing import TestingDataSet as TDS -from zipline.utils.test_utils import chrange, temp_pipeline_engine +from zipline.testing import chrange, temp_pipeline_engine class LatestTestCase(TestCase): diff --git a/tests/pipeline/test_earnings.py b/tests/pipeline/test_earnings.py index 013cf952..cad01757 100644 --- a/tests/pipeline/test_earnings.py +++ b/tests/pipeline/test_earnings.py @@ -27,7 +27,7 @@ from zipline.pipeline.loaders.earnings import EarningsCalendarLoader from zipline.pipeline.loaders.blaze import ( BlazeEarningsCalendarLoader, ) -from zipline.utils.test_utils import ( +from zipline.testing import ( make_simple_equity_info, tmp_asset_finder, ) diff --git a/tests/pipeline/test_engine.py b/tests/pipeline/test_engine.py index bde0fcdd..339f2f9b 100644 --- a/tests/pipeline/test_engine.py +++ b/tests/pipeline/test_engine.py @@ -62,13 +62,13 @@ from zipline.pipeline.factors import ( MaxDrawdown, SimpleMovingAverage, ) -from zipline.utils.memoize import lazyval -from zipline.utils.test_utils import ( +from zipline.testing import ( make_rotating_equity_info, make_simple_equity_info, product_upper_triangle, check_arrays, ) +from zipline.utils.memoize import lazyval class RollingSumDifference(CustomFactor): diff --git a/tests/pipeline/test_factor.py b/tests/pipeline/test_factor.py index d6713bc8..0722008d 100644 --- a/tests/pipeline/test_factor.py +++ b/tests/pipeline/test_factor.py @@ -22,7 +22,7 @@ from zipline.pipeline.factors import ( Returns, RSI, ) -from zipline.utils.test_utils import ( +from zipline.testing import ( check_allclose, check_arrays, parameter_space, diff --git a/tests/pipeline/test_filter.py b/tests/pipeline/test_filter.py index 28f6e2c7..755e5f3a 100644 --- a/tests/pipeline/test_filter.py +++ b/tests/pipeline/test_filter.py @@ -23,9 +23,8 @@ from numpy.random import randn, seed as random_seed from zipline.errors import BadPercentileBounds from zipline.pipeline import Filter, Factor, TermGraph -from zipline.utils.test_utils import check_arrays +from zipline.testing import check_arrays from zipline.utils.numpy_utils import float64_dtype - from .base import BasePipelineTestCase, with_default_shape diff --git a/tests/pipeline/test_numerical_expression.py b/tests/pipeline/test_numerical_expression.py index 0315d656..102f29d7 100644 --- a/tests/pipeline/test_numerical_expression.py +++ b/tests/pipeline/test_numerical_expression.py @@ -32,9 +32,8 @@ from zipline.pipeline.expression import ( NumericalExpression, NUMEXPR_MATH_FUNCS, ) - +from zipline.testing import check_arrays from zipline.utils.numpy_utils import datetime64ns_dtype, float64_dtype -from zipline.utils.test_utils import check_arrays class F(Factor): diff --git a/tests/pipeline/test_pipeline_algo.py b/tests/pipeline/test_pipeline_algo.py index eba5a254..4b3c9993 100644 --- a/tests/pipeline/test_pipeline_algo.py +++ b/tests/pipeline/test_pipeline_algo.py @@ -57,7 +57,7 @@ from zipline.pipeline.loaders.frame import DataFrameLoader from zipline.pipeline.loaders.equity_pricing_loader import ( USEquityPricingLoader, ) -from zipline.utils.test_utils import ( +from zipline.testing import ( make_simple_equity_info, str_to_seconds, ) diff --git a/tests/pipeline/test_us_equity_pricing_loader.py b/tests/pipeline/test_us_equity_pricing_loader.py index 5262d2a4..19126b35 100644 --- a/tests/pipeline/test_us_equity_pricing_loader.py +++ b/tests/pipeline/test_us_equity_pricing_loader.py @@ -54,7 +54,7 @@ from zipline.pipeline.loaders.equity_pricing_loader import ( from zipline.errors import WindowLengthTooLong from zipline.finance.trading import TradingEnvironment from zipline.pipeline.data import USEquityPricing -from zipline.utils.test_utils import ( +from zipline.testing import ( seconds_to_timestamp, str_to_seconds, ) diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index ec5f0dc5..4e22cca9 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -25,19 +25,8 @@ import numpy as np import pandas as pd from contextlib2 import ExitStack +from zipline.algorithm import TradingAlgorithm from zipline.api import FixedSlippage -from zipline.assets import Equity, Future -from zipline.utils.api_support import ZiplineAPI -from zipline.utils.control_flow import nullctx -from zipline.utils.test_utils import ( - setup_logger, - teardown_logger, - make_trade_panel_for_asset_info, - parameter_space, -) -import zipline.utils.factory as factory -import zipline.utils.simfactory as simfactory - from zipline.errors import ( OrderDuringInitialize, RegisterTradingControlPostInit, @@ -47,6 +36,18 @@ from zipline.errors import ( RootSymbolNotFound, UnsupportedDatetimeFormat, ) +from zipline.assets import Equity, Future +from zipline.finance.execution import LimitOrder +from zipline.finance.commission import PerShare +from zipline.finance.order import ORDER_STATUS +from zipline.finance.trading import SimulationParameters, TradingEnvironment +from zipline.protocol import DATASOURCE_TYPE +from zipline.sources import ( + SpecificEquityTrades, + DataFrameSource, + DataPanelSource, + RandomWalkSource, +) from zipline.test_algorithms import ( access_account_in_init, access_portfolio_in_init, @@ -85,30 +86,24 @@ from zipline.test_algorithms import ( record_float_magic, record_variables, ) -from zipline.utils.context_tricks import CallbackManager -import zipline.utils.events -from zipline.utils.test_utils import ( +from zipline.testing import ( assert_single_position, drain_zipline, make_jagged_equity_info, tmp_asset_finder, to_utc, + setup_logger, + teardown_logger, + make_trade_panel_for_asset_info, + parameter_space, ) - -from zipline.sources import (SpecificEquityTrades, - DataFrameSource, - DataPanelSource, - RandomWalkSource) - -from zipline.finance.execution import LimitOrder -from zipline.finance.trading import SimulationParameters -from zipline.finance.order import ORDER_STATUS -from zipline.utils.api_support import set_algo_instance +from zipline.utils.api_support import ZiplineAPI, set_algo_instance +from zipline.utils.context_tricks import CallbackManager +from zipline.utils.control_flow import nullctx +import zipline.utils.events from zipline.utils.events import DateRuleFactory, TimeRuleFactory, Always -from zipline.algorithm import TradingAlgorithm -from zipline.protocol import DATASOURCE_TYPE -from zipline.finance.trading import TradingEnvironment -from zipline.finance.commission import PerShare +import zipline.utils.factory as factory +import zipline.utils.simfactory as simfactory from zipline.utils.tradingcalendar import trading_day, trading_days # Because test cases appear to reuse some resources. diff --git a/tests/test_algorithm_gen.py b/tests/test_algorithm_gen.py index 177c0dc4..5d900131 100644 --- a/tests/test_algorithm_gen.py +++ b/tests/test_algorithm_gen.py @@ -26,16 +26,17 @@ import pytz from zipline.finance import trading from zipline.algorithm import TradingAlgorithm from zipline.finance import slippage -from zipline.utils import factory -from zipline.utils.factory import create_simulation_parameters -from zipline.utils.test_utils import ( +from zipline.protocol import ( + Event, + DATASOURCE_TYPE, +) +from zipline.testing import ( setup_logger, teardown_logger ) -from zipline.protocol import ( - Event, - DATASOURCE_TYPE -) +from zipline.utils import factory +from zipline.utils.factory import create_simulation_parameters + DEFAULT_TIMEOUT = 15 # seconds EXTENDED_TIMEOUT = 90 diff --git a/tests/test_assets.py b/tests/test_assets.py index ff3337c7..40cfc33e 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -72,7 +72,7 @@ from zipline.errors import ( AssetDBImpossibleDowngrade, ) from zipline.finance.trading import TradingEnvironment, noop_load -from zipline.utils.test_utils import ( +from zipline.testing import ( all_subindices, make_commodity_future_info, make_rotating_equity_info, diff --git a/tests/test_batchtransform.py b/tests/test_batchtransform.py index 92810103..a93e8b41 100644 --- a/tests/test_batchtransform.py +++ b/tests/test_batchtransform.py @@ -14,28 +14,25 @@ # limitations under the License. from collections import deque +from copy import deepcopy +from datetime import datetime +from unittest import TestCase import pytz import numpy as np import pandas as pd -from datetime import datetime -from unittest import TestCase - -from zipline.utils.test_utils import setup_logger, teardown_logger - -from zipline.sources.data_source import DataSource -import zipline.utils.factory as factory - -from zipline.transforms import batch_transform - -from zipline.test_algorithms import (BatchTransformAlgorithm, - BatchTransformAlgorithmMinute) - -from zipline.finance.trading import TradingEnvironment from zipline.algorithm import TradingAlgorithm +from zipline.finance.trading import TradingEnvironment +from zipline.sources.data_source import DataSource +from zipline.test_algorithms import ( + BatchTransformAlgorithm, + BatchTransformAlgorithmMinute, +) +from zipline.testing import setup_logger, teardown_logger +from zipline.transforms import batch_transform +import zipline.utils.factory as factory from zipline.utils.tradingcalendar import trading_days -from copy import deepcopy @batch_transform diff --git a/tests/test_blotter.py b/tests/test_blotter.py index f9606b09..f7dd9f93 100644 --- a/tests/test_blotter.py +++ b/tests/test_blotter.py @@ -28,7 +28,7 @@ from zipline.finance.execution import ( ) from zipline.sources.test_source import create_trade -from zipline.utils.test_utils import( +from zipline.testing import( setup_logger, teardown_logger, ) diff --git a/tests/test_doctests.py b/tests/test_doctests.py index 89d7f3cf..46890127 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -3,6 +3,7 @@ import sys import doctest from unittest import TestCase +from zipline import testing from zipline.lib import adjustment from zipline.pipeline import ( engine, @@ -16,7 +17,6 @@ from zipline.utils import ( memoize, numpy_utils, preprocess, - test_utils, ) @@ -66,8 +66,8 @@ class DoctestTestCase(TestCase): def test_memoize_docs(self): self._check_docs(memoize) - def test_test_utils_docs(self): - self._check_docs(test_utils) + def test_testing_docs(self): + self._check_docs(testing) def test_preprocess_docs(self): self._check_docs(preprocess) diff --git a/tests/test_exception_handling.py b/tests/test_exception_handling.py index a642e574..bcb0c569 100644 --- a/tests/test_exception_handling.py +++ b/tests/test_exception_handling.py @@ -15,24 +15,21 @@ from unittest import TestCase -import zipline.utils.simfactory as simfactory -import zipline.utils.factory as factory - +from zipline.finance.slippage import FixedSlippage +from zipline.finance.trading import TradingEnvironment from zipline.test_algorithms import ( ExceptionAlgorithm, DivByZeroAlgorithm, SetPortfolioAlgorithm, ) -from zipline.finance.slippage import FixedSlippage -from zipline.finance.trading import TradingEnvironment - - -from zipline.utils.test_utils import ( +from zipline.testing import ( drain_zipline, setup_logger, teardown_logger, ExceptionSource, ) +import zipline.utils.simfactory as simfactory +import zipline.utils.factory as factory DEFAULT_TIMEOUT = 15 # seconds EXTENDED_TIMEOUT = 90 diff --git a/tests/test_execution_styles.py b/tests/test_execution_styles.py index 5f567886..42696a7e 100644 --- a/tests/test_execution_styles.py +++ b/tests/test_execution_styles.py @@ -15,9 +15,13 @@ from unittest import TestCase +from nose_parameterized import parameterized from six.moves import range -from nose_parameterized import parameterized + +from zipline.errors import( + BadOrderParameters +) from zipline.finance.execution import ( LimitOrder, @@ -25,16 +29,11 @@ from zipline.finance.execution import ( StopLimitOrder, StopOrder, ) - -from zipline.utils.test_utils import( +from zipline.testing import( setup_logger, teardown_logger, ) -from zipline.errors import( - BadOrderParameters -) - class ExecutionStyleTestCase(TestCase): """ diff --git a/tests/test_finance.py b/tests/test_finance.py index ea3a3ce1..21e48492 100644 --- a/tests/test_finance.py +++ b/tests/test_finance.py @@ -16,40 +16,33 @@ """ Tests for the zipline.finance package """ +from datetime import datetime, timedelta import itertools import operator - -import pytz - from unittest import TestCase -from datetime import datetime, timedelta -import numpy as np -import pandas as pd from nose.tools import timed - +import numpy as np +import pandas as pd +import pytz from six.moves import range +from zipline.finance.blotter import Blotter +from zipline.finance.execution import MarketOrder, LimitOrder +from zipline.finance.trading import TradingEnvironment +from zipline.finance.performance import PerformanceTracker +from zipline.finance.trading import SimulationParameters +from zipline.gens.composites import date_sorted_sources import zipline.protocol from zipline.protocol import Event, DATASOURCE_TYPE - -import zipline.utils.factory as factory -import zipline.utils.simfactory as simfactory - -from zipline.finance.blotter import Blotter -from zipline.gens.composites import date_sorted_sources - -from zipline.finance.trading import TradingEnvironment -from zipline.finance.execution import MarketOrder, LimitOrder -from zipline.finance.trading import SimulationParameters - -from zipline.finance.performance import PerformanceTracker -from zipline.utils.test_utils import( +from zipline.testing import( setup_logger, teardown_logger, assert_single_position ) +import zipline.utils.factory as factory +import zipline.utils.simfactory as simfactory DEFAULT_TIMEOUT = 15 # seconds EXTENDED_TIMEOUT = 90 diff --git a/tests/test_history.py b/tests/test_history.py index 9179d3c7..e1ab017e 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -38,8 +38,8 @@ from zipline.history import history from zipline.history.history_container import HistoryContainer from zipline.protocol import BarData from zipline.sources import RandomWalkSource, DataFrameSource +from zipline.testing import subtest import zipline.utils.factory as factory -from zipline.utils.test_utils import subtest class TestHistoryContainer(TestCase): diff --git a/tests/test_security_list.py b/tests/test_security_list.py index e0bd58fd..a97a6dc2 100644 --- a/tests/test_security_list.py +++ b/tests/test_security_list.py @@ -5,13 +5,19 @@ from unittest import TestCase from zipline.algorithm import TradingAlgorithm from zipline.errors import TradingControlViolation -from zipline.sources import SpecificEquityTrades from zipline.finance.trading import TradingEnvironment -from zipline.utils.test_utils import ( - setup_logger, teardown_logger, security_list_copy, add_security_data,) +from zipline.sources import SpecificEquityTrades +from zipline.testing import ( + add_security_data, + security_list_copy, + setup_logger, + teardown_logger, +) from zipline.utils import factory from zipline.utils.security_list import ( - SecurityListSet, load_from_directory) + SecurityListSet, + load_from_directory, +) LEVERAGED_ETFS = load_from_directory('leveraged_etf_list') diff --git a/tests/test_test_utils.py b/tests/test_testing.py similarity index 95% rename from tests/test_test_utils.py rename to tests/test_testing.py index e62dc7a3..b72e152e 100644 --- a/tests/test_test_utils.py +++ b/tests/test_testing.py @@ -3,7 +3,8 @@ Tests for our testing utilities. """ from itertools import product from unittest import TestCase -from zipline.utils.test_utils import parameter_space + +from zipline.testing import parameter_space class TestParameterSpace(TestCase): diff --git a/tests/test_transforms_talib.py b/tests/test_transforms_talib.py index 366abadc..a177e328 100644 --- a/tests/test_transforms_talib.py +++ b/tests/test_transforms_talib.py @@ -12,23 +12,19 @@ # 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 pytz -import numpy as np -import pandas as pd -import talib - from datetime import timedelta, datetime from unittest import TestCase, skip -from zipline.utils.test_utils import setup_logger, teardown_logger +import numpy as np +import pandas as pd +import pytz +import talib -import zipline.utils.factory as factory from zipline.finance.trading import TradingEnvironment - from zipline.test_algorithms import TALIBAlgorithm - +from zipline.testing import setup_logger, teardown_logger import zipline.transforms.ta as ta +import zipline.utils.factory as factory class TestTALIB(TestCase): diff --git a/tests/utils/test_events.py b/tests/utils/test_events.py index 4c5c5b3c..6cfda4b1 100644 --- a/tests/utils/test_events.py +++ b/tests/utils/test_events.py @@ -25,6 +25,7 @@ from six import iteritems from six.moves import range, map from zipline.finance.trading import TradingEnvironment +from zipline.testing import subtest import zipline.utils.events from zipline.utils.events import ( EventRule, @@ -49,7 +50,6 @@ from zipline.utils.events import ( MAX_MONTH_RANGE, MAX_WEEK_RANGE, ) -from zipline.utils.test_utils import subtest # A day known to be a half day. diff --git a/tests/utils/test_final.py b/tests/utils/test_final.py new file mode 100644 index 00000000..cb27baf0 --- /dev/null +++ b/tests/utils/test_final.py @@ -0,0 +1,264 @@ +from abc import abstractmethod, ABCMeta +from unittest import TestCase + +from six import with_metaclass + +from zipline.utils.final import ( + FinalMeta, + final_meta_factory, + final, +) + + +class FinalMetaTestCase(TestCase): + @classmethod + def setUpClass(cls): + class ClassWithFinal(with_metaclass(FinalMeta, object)): + a = final('ClassWithFinal: a') + b = 'ClassWithFinal: b' + + @final + def f(self): + return 'ClassWithFinal: f' + + def g(self): + return 'ClassWithFinal: g' + + cls.class_ = ClassWithFinal + + def test_subclass_no_override(self): + """ + Tests that it is valid to create a subclass that does not override + any methods. + """ + class SubClass(self.class_): + pass + + def test_subclass_no_final_override(self): + """ + Tests that it is valid to create a subclass that does not override + and final methods. + """ + class SubClass(self.class_): + b = 'SubClass: b' + + def g(self): + return 'SubClass: g' + + def test_override_final_no_decorator(self): + """ + Tests that attempting to create a subclass that overrides a final + method will raise a `TypeError`. + """ + with self.assertRaises(TypeError): + class SubClass(self.class_): + def f(self): + return 'SubClass: f' + + def test_override_final_attribute(self): + """ + Tests that attempting to create a subclass that overrides a final + attribute will raise a `TypeError`. + """ + with self.assertRaises(TypeError): + class SubClass(self.class_): + a = 'SubClass: a' + + def test_override_final_with_decorator(self): + """ + Tests that attempting to create a subclass that overrides a final + method will raise a `TypeError` even if you mark the new version as + final. + """ + with self.assertRaises(TypeError): + class SubClass(self.class_): + @final + def f(self): + return 'SubClass: f' + + def test_override_final_attribute_with_final(self): + """ + Tests that attempting to create a subclass that overrides a final + attribute will raise a `TypeError` even if you mark the new version as + final. + """ + with self.assertRaises(TypeError): + class SubClass(self.class_): + a = final('SubClass: a') + + def test_override_on_class_object(self): + """ + Tests overriding final methods and attributes on the class object + itself. + """ + class SubClass(self.class_): + pass + + with self.assertRaises(TypeError): + SubClass.f = lambda self: 'SubClass: f' + + with self.assertRaises(TypeError): + SubClass.a = 'SubClass: a' + + def test_override_on_instance(self): + """ + Tests overriding final methods on instances of a class. + """ + class SubClass(self.class_): + def h(self): + pass + + s = SubClass() + with self.assertRaises(TypeError): + s.f = lambda self: 'SubClass: f' + + with self.assertRaises(TypeError): + s.a = lambda self: 'SubClass: a' + + def test_override_on_super(self): + """ + Tests overriding on the class that has the @final methods in it. + """ + old_a = self.class_.a + old_f = self.class_.f + try: + with self.assertRaises(TypeError): + self.class_.f = lambda *args: None + except Exception: + self.class_.f = old_f + raise + + try: + with self.assertRaises(TypeError): + self.class_.a = 'SubClass: a' + except Exception: + self.class_.a = old_a + raise + + def test_override___setattr___on_subclass(self): + """ + Tests an attempt to override __setattr__ which is implicitly final. + """ + with self.assertRaises(TypeError): + class SubClass(self.class_): + def __setattr__(self, name, value): + object.__setattr__(self, name, value) + + def test_override___setattr___on_instance(self): + """ + Tests overriding __setattr__ on an instance. + """ + class SubClass(self.class_): + pass + + s = SubClass() + with self.assertRaises(TypeError): + s.__setattr__ = lambda a, b: None + + +class FinalABCMetaTestCase(FinalMetaTestCase): + @classmethod + def setUpClass(cls): + FinalABCMeta = final_meta_factory(ABCMeta) + + class ABCWithFinal(with_metaclass(FinalABCMeta, object)): + a = final('ABCWithFinal: a') + b = 'ABCWithFinal: b' + + @final + def f(self): + return 'ABCWithFinal: f' + + def g(self): + return 'ABCWithFinal: g' + + @abstractmethod + def h(self): + raise NotImplementedError('h') + + cls.class_ = ABCWithFinal + + def test_cannot_instantiate_subclass(self): + """ + Tests that you cannot create an instance of a subclass + that does not implement the abstractmethod h. + """ + class AbstractSubClass(self.class_): + pass + + with self.assertRaises(TypeError): + AbstractSubClass() + + def test_override_on_instance(self): + class SubClass(self.class_): + def h(self): + """ + Pass the abstract tests by creating this method. + """ + pass + + s = SubClass() + with self.assertRaises(TypeError): + s.f = lambda self: 'SubClass: f' + + def test_override___setattr___on_instance(self): + """ + Tests overriding __setattr__ on an instance. + """ + class SubClass(self.class_): + def h(self): + pass + + s = SubClass() + with self.assertRaises(TypeError): + s.__setattr__ = lambda a, b: None + + def test_subclass_setattr(self): + """ + Tests that subclasses don't destroy the __setattr__. + """ + class ClassWithFinal(with_metaclass(FinalMeta, object)): + @final + def f(self): + return 'ClassWithFinal: f' + + class SubClass(ClassWithFinal): + def __init__(self): + self.a = 'a' + + SubClass() + self.assertEqual(SubClass().a, 'a') + self.assertEqual(SubClass().f(), 'ClassWithFinal: f') + + def test_final_classmethod(self): + + class ClassWithClassMethod(with_metaclass(FinalMeta, object)): + count = 0 + + @final + @classmethod + def f(cls): + cls.count += 1 + return cls.count + + with self.assertRaises(TypeError): + class ClassOverridingClassMethod(ClassWithClassMethod): + @classmethod + def f(cls): + return "Oh Noes!" + + with self.assertRaises(TypeError): + ClassWithClassMethod.f = lambda cls: 0 + + self.assertEqual(ClassWithClassMethod.f(), 1) + self.assertEqual(ClassWithClassMethod.f(), 2) + self.assertEqual(ClassWithClassMethod.f(), 3) + + instance = ClassWithClassMethod() + + with self.assertRaises(TypeError): + instance.f = lambda cls: 0 + + self.assertEqual(ClassWithClassMethod.f(), 4) + self.assertEqual(ClassWithClassMethod.f(), 5) + self.assertEqual(ClassWithClassMethod.f(), 6) diff --git a/zipline/testing/__init__.py b/zipline/testing/__init__.py new file mode 100644 index 00000000..16bc73d7 --- /dev/null +++ b/zipline/testing/__init__.py @@ -0,0 +1,37 @@ +from .core import ( # noqa + EPOCH, + ExceptionSource, + ExplodingObject, + add_security_data, + all_pairs_matching_predicate, + all_subindices, + assert_single_position, + assert_timestamp_equal, + check_allclose, + check_arrays, + chrange, + drain_zipline, + gen_calendars, + make_commodity_future_info, + make_future_info, + make_jagged_equity_info, + make_rotating_equity_info, + make_simple_equity_info, + make_test_handler, + make_trade_panel_for_asset_info, + num_days_in_range, + parameter_space, + powerset, + product_upper_triangle, + seconds_to_timestamp, + security_list_copy, + setup_logger, + str_to_seconds, + subtest, + teardown_logger, + temp_pipeline_engine, + tmp_asset_finder, + tmp_assets_db, + to_series, + to_utc, +) diff --git a/zipline/utils/test_utils.py b/zipline/testing/core.py similarity index 99% rename from zipline/utils/test_utils.py rename to zipline/testing/core.py index eb9adaa6..ea26bceb 100644 --- a/zipline/utils/test_utils.py +++ b/zipline/testing/core.py @@ -210,7 +210,7 @@ def all_pairs_matching_predicate(values, pred): Examples -------- - >>> from zipline.utils.test_utils import all_pairs_matching_predicate + >>> from zipline.testing import all_pairs_matching_predicate >>> from operator import eq, lt >>> list(all_pairs_matching_predicate(range(5), eq)) [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)] @@ -665,7 +665,7 @@ def subtest(iterator, *_names): """ Construct a subtest in a unittest. - Consider using ``zipline.utils.test_utils.parameter_space`` when subtests + Consider using ``zipline.testing.parameter_space`` when subtests are constructed over a single input or over the cross-product of multiple inputs. @@ -718,7 +718,7 @@ def subtest(iterator, *_names): See Also -------- - zipline.utils.test_utils.parameter_space + zipline.testing.parameter_space """ def dec(f): @wraps(f) @@ -848,7 +848,7 @@ def parameter_space(**params): See Also -------- - zipline.utils.test_utils.subtest + zipline.testing.subtest """ def decorator(f): diff --git a/zipline/testing/fixtures.py b/zipline/testing/fixtures.py new file mode 100644 index 00000000..76fb6bcb --- /dev/null +++ b/zipline/testing/fixtures.py @@ -0,0 +1,158 @@ +from unittest import TestCase + +from contextlib2 import ExitStack +import pandas as pd +from six import with_metaclass + +from .core import tmp_asset_finder +from ..utils import tradingcalendar +from ..utils.final import FinalMeta, final + + +class ZiplineTestCase(with_metaclass(FinalMeta, TestCase)): + """ + Shared extensions to core unittest.TestCase. + + Overrides the default unittest setUp/tearDown functions with versions that + use ExitStack to correctly clean up resources, even in the face of + exceptions that occur during setUp/setUpClass. + + Subclasses **should not override setUp or setUpClass**! + + Instead, they should implement `init_instance_fixtures` for per-test-method + resources, and `init_class_fixtures` for per-class resources. + + Resources that need to be cleaned up should be registered using + either `enter_{class,instance}_context` or `add_{class,instance}_callback}. + """ + + @final + @classmethod + def setUpClass(cls): + cls._class_teardown_stack = ExitStack() + try: + cls._base_init_fixtures_was_called = False + cls.init_class_fixtures() + assert cls._base_init_fixtures_was_called, ( + "ZiplineTestCase.init_class_fixtures() was not called.\n" + "This probably means that you overrode init_class_fixtures" + " without calling super()." + ) + except: + cls.tearDownClass() + raise + + @classmethod + def init_class_fixtures(cls): + """ + Override and implement this classmethod to register resources that + should be created and/or torn down on a per-class basis. + + Subclass implementations of this should always invoke this with super() + to ensure that fixture mixins work properly. + """ + cls._base_init_fixtures_was_called = True + + @final + @classmethod + def tearDownClass(cls): + cls._class_teardown_stack.close() + + @final + @classmethod + def enter_class_context(cls, context_manager): + """ + Enter a context manager to be exited during the tearDownClass + """ + return cls._class_teardown_stack.enter_context(context_manager) + + @final + @classmethod + def add_class_callback(cls, callback): + """ + Register a callback to be executed during tearDownClass. + """ + return cls._class_teardown_stack.callback(callback) + + @final + def setUp(self): + self._instance_teardown_stack = ExitStack() + try: + self._init_instance_fixtures_was_called = False + self.init_instance_fixtures() + assert self._init_instance_fixtures_was_called, ( + "ZiplineTestCase.init_instance_fixtures() was not" + " called.\n" + "This probably means that you overrode" + " init_instance_fixtures without calling super()." + ) + except: + self.tearDown() + raise + + def init_instance_fixtures(self): + self._init_instance_fixtures_was_called = True + + @final + def tearDown(self): + self._instance_teardown_stack.close() + + @final + def enter_instance_context(self, context_manager): + """ + Enter a context manager that should be exited during tearDown. + """ + return self._instance_teardown_stack.enter_context(context_manager) + + @final + def add_instance_callback(self, callback): + """ + Register a callback to be executed during tearDown. + """ + return self._instance_teardown_stack.callback(callback) + + +class WithAssetFinder(object): + """ + ZiplineTestCase mixin providing cls.asset_finder as a class-level fixture. + + After init_class_fixtures has been called, `cls.asset_finder` is populated + with an AssetFinder. The default finder just a `tmp_asset_finder` + + This behavior can be altered by overriding `make_asset_finder` as a class + method. + """ + @classmethod + def make_asset_finder(cls): + return cls.enter_class_context(tmp_asset_finder()) + + @classmethod + def init_class_fixtures(cls): + super(WithAssetFinder, cls).init_class_fixtures() + cls.asset_finder = cls.make_asset_finder() + + +class WithNYSETradingDays(object): + """ + ZiplineTestCase mixin providing cls.trading_days as a class-level fixture. + + After init_class_fixtures has been called, `cls.trading_days` is populated + with a DatetimeIndex containing NYSE calendar trading days ranging from: + + (DATA_MAX_DAY - (cls.TRADING_DAY_COUNT) -> DATA_MAX_DAY) + + The default value of TRADING_DAY_COUNT is 126 (half a trading-year). + Inheritors can override TRADING_DAY_COUNT to request more or less data. + """ + DATA_MAX_DAY = pd.Timestamp('2015-01-02') + TRADING_DAY_COUNT = 126 + + @classmethod + def init_class_fixtures(cls): + super(WithNYSETradingDays, cls).init_class_fixtures() + + all_days = tradingcalendar.trading_days + end_loc = all_days.get_loc(cls.DATA_MAX_DAY) + start_loc = end_loc - cls.TRADING_DAY_COUNT + + cls.trading_days = all_days[start_loc:end_loc + 1] diff --git a/zipline/utils/final.py b/zipline/utils/final.py new file mode 100644 index 00000000..02394535 --- /dev/null +++ b/zipline/utils/final.py @@ -0,0 +1,138 @@ +from abc import ABCMeta, abstractmethod +from weakref import WeakKeyDictionary + +from six import with_metaclass, iteritems +from toolz import memoize + +# Consistent error to be thrown in various cases regarding overriding +# `final` attributes. +_type_error = TypeError('Cannot override final attribute') + + +def bases_mro(bases): + """ + Yield classes in the order that methods should be looked up from the + base classes of an object. + """ + for base in bases: + for class_ in base.__mro__: + yield class_ + + +def is_final(name, mro): + """ + Checks if `name` is a `final` object in the given `mro`. + We need to check the mro because we need to directly go into the __dict__ + of the classes. Because `final` objects are descriptor, we need to grab + them _BEFORE_ the `__call__` is invoked. + """ + return any(isinstance(getattr(c, '__dict__', {}).get(name), final) + for c in bases_mro(mro)) + + +@memoize(cache=WeakKeyDictionary()) +def final_meta_factory(base): + """ + Creates a metaclass that inherits from `base` that also checks for `final` + attributes. + + This will cause class construction to fail if the class attempts to + override a final method or attribute. + """ + class _FinalMeta(base): + def __new__(mcls, name, bases, dict_): + for k, v in iteritems(dict_): + if is_final(k, bases): + raise _type_error + + setattr_ = dict_.get('__setattr__') + if setattr_ is None: + # No `__setattr__` was explicitly defined, look up the super + # class's. `bases[0]` will have a `__setattr__` because + # `object` does so we don't need to worry about the mro. + setattr_ = bases[0].__setattr__ + + if not is_final('__setattr__', bases) \ + and not isinstance(setattr_, final): + # implicitly make the `__setattr__` a `final` object so that + # users cannot just avoid the descriptor protocol. + dict_['__setattr__'] = final(setattr_) + + return base.__new__(mcls, name, bases, dict_) + + def __setattr__(self, name, value): + """ + This stops the `final` attributes from being reassigned on the + class object. + """ + if is_final(name, self.__mro__): + raise _type_error + + base.__setattr__(self, name, value) + + _FinalMeta.__name__ = '%sFinalMeta' % base.__name__ + return _FinalMeta + + +FinalMeta = final_meta_factory(type) + + +class final(with_metaclass(ABCMeta)): + """ + An attribute that cannot be overridden. + This is like the final modifier in Java. + + Example usage: + >>> from six import with_metaclass + >>> class C(with_metaclass(FinalMeta)): + ... @final + ... def f(self): + ... return 'value' + ... + + This constructs a class with final method `f`. This cannot be overridden + on the class object or on any instance. You cannot override this by + subclassing `C`; attempting to do so will raise a `TypeError` at class + construction time. + """ + def __new__(cls, attr): + # Decide if this is a method wrapper or an attribute wrapper. + # We are going to cache the `callable` check by creating a + # method or attribute wrapper. + if hasattr(attr, '__get__'): + return object.__new__(finaldescriptor) + else: + return object.__new__(finalvalue) + + def __init__(self, attr): + self._attr = attr + + def __set__(self, instance, value): + """ + `final` objects cannot be reassigned. This is the most import concept + about `final`s. + + Unlike a `property` object, this will raise a `TypeError` when you + attempt to reassign it. + """ + raise _type_error + + @abstractmethod + def __get__(self, instance, owner): + raise NotImplementedError('__get__') + + +class finalvalue(final): + """ + A wrapper for a non-descriptor attribute. + """ + def __get__(self, instance, owner): + return self._attr + + +class finaldescriptor(final): + """ + A final wrapper around a descriptor. + """ + def __get__(self, instance, owner): + return self._attr.__get__(instance, owner)