Compare commits
	
		
			No commits in common. "ce79346f76c11d0473bcf7378d05870cef850b64" and "1bfab90d35a6e5739c824f8f0d830b2156e7eb7f" have entirely different histories.
		
	
	
		
			ce79346f76
			...
			1bfab90d35
		
	
		
					 11 changed files with 2 additions and 439 deletions
				
			
		
							
								
								
									
										11
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								CHANGELOG.md
									
										
									
									
									
								
							|  | @ -5,17 +5,6 @@ All notable changes to WuttJamaican will be documented in this file. | ||||||
| The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) | ||||||
| and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). | ||||||
| 
 | 
 | ||||||
| ## v0.20.0 (2025-01-11) |  | ||||||
| 
 |  | ||||||
| ### Feat |  | ||||||
| 
 |  | ||||||
| - add basic support for "reports" feature |  | ||||||
| 
 |  | ||||||
| ### Fix |  | ||||||
| 
 |  | ||||||
| - add `render_percent()` method for app handler |  | ||||||
| - set global default sender to root@localhost |  | ||||||
| 
 |  | ||||||
| ## v0.19.3 (2025-01-09) | ## v0.19.3 (2025-01-09) | ||||||
| 
 | 
 | ||||||
| ### Fix | ### Fix | ||||||
|  |  | ||||||
|  | @ -1,6 +0,0 @@ | ||||||
| 
 |  | ||||||
| ``wuttjamaican.reports`` |  | ||||||
| ======================== |  | ||||||
| 
 |  | ||||||
| .. automodule:: wuttjamaican.reports |  | ||||||
|    :members: |  | ||||||
|  | @ -247,33 +247,6 @@ Glossary | ||||||
|      portion of the :term:`app`.  Similar to a "plugin" concept; see |      portion of the :term:`app`.  Similar to a "plugin" concept; see | ||||||
|      :doc:`narr/providers/index`. |      :doc:`narr/providers/index`. | ||||||
| 
 | 
 | ||||||
|    report |  | ||||||
|      The concept of a report is intentionally vague, in the context of |  | ||||||
|      WuttJamaican.  Basically it is something which can be "ran" |  | ||||||
|      (usually with :term:`report params`) to generate a data set.  The |  | ||||||
|      output can be viewed in the app UI, or it can be saved to file. |  | ||||||
| 
 |  | ||||||
|      The base class is :class:`~wuttjamaican.reports.Report`.  See |  | ||||||
|      also :term:`report handler`. |  | ||||||
| 
 |  | ||||||
|    report handler |  | ||||||
|      The :term:`handler` responsible for running :term:`reports |  | ||||||
|      <report>`, for display in app UI or saved to file etc. |  | ||||||
| 
 |  | ||||||
|      Base class is :class:`~wuttjamaican.reports.ReportHandler`. |  | ||||||
| 
 |  | ||||||
|    report key |  | ||||||
|      Unique key which identifies a particular :term:`report`. |  | ||||||
| 
 |  | ||||||
|    report module |  | ||||||
|      This refers to a Python module which contains :term:`report` |  | ||||||
|      definitions. |  | ||||||
| 
 |  | ||||||
|    report params |  | ||||||
|      This refers to the input parameters used when running a |  | ||||||
|      :term:`report`.  It is usually a simple mapping of key/value |  | ||||||
|      pairs. |  | ||||||
| 
 |  | ||||||
|    settings table |    settings table | ||||||
|      Table in the :term:`app database` which is used to store |      Table in the :term:`app database` which is used to store | ||||||
|      :term:`config settings<config setting>`.  See also |      :term:`config settings<config setting>`.  See also | ||||||
|  |  | ||||||
|  | @ -87,6 +87,5 @@ Contents | ||||||
|    api/wuttjamaican.install |    api/wuttjamaican.install | ||||||
|    api/wuttjamaican.people |    api/wuttjamaican.people | ||||||
|    api/wuttjamaican.progress |    api/wuttjamaican.progress | ||||||
|    api/wuttjamaican.reports |  | ||||||
|    api/wuttjamaican.testing |    api/wuttjamaican.testing | ||||||
|    api/wuttjamaican.util |    api/wuttjamaican.util | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ build-backend = "hatchling.build" | ||||||
| 
 | 
 | ||||||
| [project] | [project] | ||||||
| name = "WuttJamaican" | name = "WuttJamaican" | ||||||
| version = "0.20.0" | version = "0.19.3" | ||||||
| description = "Base package for Wutta Framework" | description = "Base package for Wutta Framework" | ||||||
| readme = "README.md" | readme = "README.md" | ||||||
| authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}] | authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}] | ||||||
|  |  | ||||||
|  | @ -88,7 +88,6 @@ class AppHandler: | ||||||
|     default_email_handler_spec = 'wuttjamaican.email:EmailHandler' |     default_email_handler_spec = 'wuttjamaican.email:EmailHandler' | ||||||
|     default_install_handler_spec = 'wuttjamaican.install:InstallHandler' |     default_install_handler_spec = 'wuttjamaican.install:InstallHandler' | ||||||
|     default_people_handler_spec = 'wuttjamaican.people:PeopleHandler' |     default_people_handler_spec = 'wuttjamaican.people:PeopleHandler' | ||||||
|     default_report_handler_spec = 'wuttjamaican.reports:ReportHandler' |  | ||||||
| 
 | 
 | ||||||
|     def __init__(self, config): |     def __init__(self, config): | ||||||
|         self.config = config |         self.config = config | ||||||
|  | @ -772,22 +771,6 @@ class AppHandler: | ||||||
|         """ |         """ | ||||||
|         return simple_error(error) |         return simple_error(error) | ||||||
| 
 | 
 | ||||||
|     def render_percent(self, value, decimals=2): |  | ||||||
|         """ |  | ||||||
|         Return a human-friendly display string for the given |  | ||||||
|         percentage value, e.g. ``23.45139`` becomes ``"23.45 %"``. |  | ||||||
| 
 |  | ||||||
|         :param value: The value to be rendered. |  | ||||||
| 
 |  | ||||||
|         :returns: Display string for the percentage value. |  | ||||||
|         """ |  | ||||||
|         if value is None: |  | ||||||
|             return "" |  | ||||||
|         fmt = f'{{:0.{decimals}f}} %' |  | ||||||
|         if value < 0: |  | ||||||
|             return f'({fmt.format(-value)})' |  | ||||||
|         return fmt.format(value) |  | ||||||
| 
 |  | ||||||
|     def render_quantity(self, value, empty_zero=False): |     def render_quantity(self, value, empty_zero=False): | ||||||
|         """ |         """ | ||||||
|         Return a human-friendly display string for the given quantity |         Return a human-friendly display string for the given quantity | ||||||
|  | @ -896,19 +879,6 @@ class AppHandler: | ||||||
|             self.handlers['people'] = factory(self.config, **kwargs) |             self.handlers['people'] = factory(self.config, **kwargs) | ||||||
|         return self.handlers['people'] |         return self.handlers['people'] | ||||||
| 
 | 
 | ||||||
|     def get_report_handler(self, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Get the configured :term:`report handler`. |  | ||||||
| 
 |  | ||||||
|         :rtype: :class:`~wuttjamaican.reports.ReportHandler` |  | ||||||
|         """ |  | ||||||
|         if 'reports' not in self.handlers: |  | ||||||
|             spec = self.config.get(f'{self.appname}.reports.handler_spec', |  | ||||||
|                                    default=self.default_report_handler_spec) |  | ||||||
|             factory = self.load_object(spec) |  | ||||||
|             self.handlers['reports'] = factory(self.config, **kwargs) |  | ||||||
|         return self.handlers['reports'] |  | ||||||
| 
 |  | ||||||
|     ############################## |     ############################## | ||||||
|     # convenience delegators |     # convenience delegators | ||||||
|     ############################## |     ############################## | ||||||
|  |  | ||||||
|  | @ -1,228 +0,0 @@ | ||||||
| # -*- coding: utf-8; -*- |  | ||||||
| ################################################################################ |  | ||||||
| # |  | ||||||
| #  WuttJamaican -- Base package for Wutta Framework |  | ||||||
| #  Copyright © 2023-2024 Lance Edgar |  | ||||||
| # |  | ||||||
| #  This file is part of Wutta Framework. |  | ||||||
| # |  | ||||||
| #  Wutta Framework is free software: you can redistribute it and/or modify it |  | ||||||
| #  under the terms of the GNU General Public License as published by the Free |  | ||||||
| #  Software Foundation, either version 3 of the License, or (at your option) any |  | ||||||
| #  later version. |  | ||||||
| # |  | ||||||
| #  Wutta Framework is distributed in the hope that it will be useful, but |  | ||||||
| #  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |  | ||||||
| #  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for |  | ||||||
| #  more details. |  | ||||||
| # |  | ||||||
| #  You should have received a copy of the GNU General Public License along with |  | ||||||
| #  Wutta Framework.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
| # |  | ||||||
| ################################################################################ |  | ||||||
| """ |  | ||||||
| Report Utilities |  | ||||||
| """ |  | ||||||
| 
 |  | ||||||
| import importlib |  | ||||||
| 
 |  | ||||||
| from wuttjamaican.app import GenericHandler |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class Report: |  | ||||||
|     """ |  | ||||||
|     Base class for all :term:`reports <report>`. |  | ||||||
| 
 |  | ||||||
|     .. attribute:: report_key |  | ||||||
| 
 |  | ||||||
|        Each report must define a unique key, to identify it. |  | ||||||
| 
 |  | ||||||
|     .. attribute:: report_title |  | ||||||
| 
 |  | ||||||
|        This is the common display title for the report. |  | ||||||
|     """ |  | ||||||
|     report_title = "Untitled Report" |  | ||||||
| 
 |  | ||||||
|     def __init__(self, config): |  | ||||||
|         self.config = config |  | ||||||
|         self.app = config.get_app() |  | ||||||
| 
 |  | ||||||
|     def add_params(self, schema): |  | ||||||
|         """ |  | ||||||
|         Add field nodes to the given schema, defining all |  | ||||||
|         :term:`report params`. |  | ||||||
| 
 |  | ||||||
|         :param schema: :class:`~colander:colander.Schema` instance. |  | ||||||
| 
 |  | ||||||
|         The schema is from Colander so nodes must be compatible with |  | ||||||
|         that; for instance:: |  | ||||||
| 
 |  | ||||||
|            import colander |  | ||||||
| 
 |  | ||||||
|            def add_params(self, schema): |  | ||||||
| 
 |  | ||||||
|                schema.add(colander.SchemaNode( |  | ||||||
|                    colander.Date(), |  | ||||||
|                    name='start_date')) |  | ||||||
| 
 |  | ||||||
|                schema.add(colander.SchemaNode( |  | ||||||
|                    colander.Date(), |  | ||||||
|                    name='end_date')) |  | ||||||
|         """ |  | ||||||
| 
 |  | ||||||
|     def get_output_columns(self): |  | ||||||
|         """ |  | ||||||
|         This should return a list of column definitions to be used |  | ||||||
|         when displaying or persisting the data output. |  | ||||||
| 
 |  | ||||||
|         Each entry can be a simple column name, or else a dict with |  | ||||||
|         other options, e.g.:: |  | ||||||
| 
 |  | ||||||
|            def get_output_columns(self): |  | ||||||
|                return [ |  | ||||||
|                    'foo', |  | ||||||
|                    {'name': 'bar', |  | ||||||
|                     'label': "BAR"}, |  | ||||||
|                    {'name': 'sales', |  | ||||||
|                     'label': "Total Sales", |  | ||||||
|                     'numeric': True, |  | ||||||
|                     'formatter': self.app.render_currency}, |  | ||||||
|                ] |  | ||||||
| 
 |  | ||||||
|         :returns: List of column definitions as described above. |  | ||||||
| 
 |  | ||||||
|         The last entry shown above has all options currently |  | ||||||
|         supported; here we explain those: |  | ||||||
| 
 |  | ||||||
|         * ``name`` - True name for the column. |  | ||||||
| 
 |  | ||||||
|         * ``label`` - Display label for the column.  If not specified, |  | ||||||
|           one is derived from the ``name``. |  | ||||||
| 
 |  | ||||||
|         * ``numeric`` - Boolean indicating the column data is numeric, |  | ||||||
|           so should be right-aligned. |  | ||||||
| 
 |  | ||||||
|         * ``formatter`` - Custom formatter / value rendering callable |  | ||||||
|           for the column.  If set, this will be called with just one |  | ||||||
|           arg (the value) for each data row. |  | ||||||
|         """ |  | ||||||
|         raise NotImplementedError |  | ||||||
| 
 |  | ||||||
|     def make_data(self, params, progress=None): |  | ||||||
|         """ |  | ||||||
|         This must "run" the report and return the final data. |  | ||||||
| 
 |  | ||||||
|         Note that this should *not* (usually) write the data to file, |  | ||||||
|         its purpose is just to obtain the data. |  | ||||||
| 
 |  | ||||||
|         The return value should usually be a dict, with no particular |  | ||||||
|         structure required beyond that.  However it also can be a list |  | ||||||
|         of data rows. |  | ||||||
| 
 |  | ||||||
|         There is no default logic here; subclass must define. |  | ||||||
| 
 |  | ||||||
|         :param params: Dict of :term:`report params`. |  | ||||||
| 
 |  | ||||||
|         :param progress: Optional progress indicator factory. |  | ||||||
| 
 |  | ||||||
|         :returns: Data dict, or list of rows. |  | ||||||
|         """ |  | ||||||
|         raise NotImplementedError |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ReportHandler(GenericHandler): |  | ||||||
|     """ |  | ||||||
|     Base class and default implementation for the :term:`report |  | ||||||
|     handler`. |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     def get_report_modules(self): |  | ||||||
|         """ |  | ||||||
|         Returns a list of all known :term:`report modules <report |  | ||||||
|         module>`. |  | ||||||
| 
 |  | ||||||
|         This will discover all report modules exposed by the |  | ||||||
|         :term:`app`, and/or its :term:`providers <provider>`. |  | ||||||
|         """ |  | ||||||
|         if not hasattr(self, '_report_modules'): |  | ||||||
|             self._report_modules = [] |  | ||||||
|             for provider in self.app.providers.values(): |  | ||||||
|                 if hasattr(provider, 'report_modules'): |  | ||||||
|                     modules = provider.report_modules |  | ||||||
|                     if modules: |  | ||||||
|                         if isinstance(modules, str): |  | ||||||
|                             modules = [modules] |  | ||||||
|                         for module in modules: |  | ||||||
|                             module = importlib.import_module(module) |  | ||||||
|                             self._report_modules.append(module) |  | ||||||
| 
 |  | ||||||
|         return self._report_modules |  | ||||||
| 
 |  | ||||||
|     def get_reports(self): |  | ||||||
|         """ |  | ||||||
|         Returns a dict of all known :term:`reports <report>`, keyed by |  | ||||||
|         :term:`report key`. |  | ||||||
| 
 |  | ||||||
|         This calls :meth:`get_report_modules()` and for each module, |  | ||||||
|         it discovers all the reports it contains. |  | ||||||
|         """ |  | ||||||
|         if not hasattr(self, '_reports'): |  | ||||||
|             self._reports = {} |  | ||||||
|             for module in self.get_report_modules(): |  | ||||||
|                 for name in dir(module): |  | ||||||
|                     obj = getattr(module, name) |  | ||||||
|                     if (isinstance(obj, type) |  | ||||||
|                         and obj is not Report |  | ||||||
|                         and issubclass(obj, Report)): |  | ||||||
|                         self._reports[obj.report_key] = obj |  | ||||||
| 
 |  | ||||||
|         return self._reports |  | ||||||
| 
 |  | ||||||
|     def get_report(self, key, instance=True): |  | ||||||
|         """ |  | ||||||
|         Fetch the :term:`report` class or instance for given key. |  | ||||||
| 
 |  | ||||||
|         :param key: Identifying :term:`report key`. |  | ||||||
| 
 |  | ||||||
|         :param instance: Whether to return the class, or an instance. |  | ||||||
|            Default is ``True`` which means return the instance. |  | ||||||
| 
 |  | ||||||
|         :returns: :class:`Report` class or instance, or ``None`` if |  | ||||||
|            the report could not be found. |  | ||||||
|         """ |  | ||||||
|         reports = self.get_reports() |  | ||||||
|         if key in reports: |  | ||||||
|             report = reports[key] |  | ||||||
|             if instance: |  | ||||||
|                 report = report(self.config) |  | ||||||
|             return report |  | ||||||
| 
 |  | ||||||
|     def make_report_data(self, report, params=None, progress=None, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Run the given report and return the output data. |  | ||||||
| 
 |  | ||||||
|         This calls :meth:`Report.make_data()` on the report, and |  | ||||||
|         tweaks the output as needed for consistency.  The return value |  | ||||||
|         should resemble this structure:: |  | ||||||
| 
 |  | ||||||
|            { |  | ||||||
|                'output_title': "My Report", |  | ||||||
|                'data': ..., |  | ||||||
|            } |  | ||||||
| 
 |  | ||||||
|         However that is the *minimum*; the dict may have other keys as |  | ||||||
|         well. |  | ||||||
| 
 |  | ||||||
|         :param report: :class:`Report` instance to run. |  | ||||||
| 
 |  | ||||||
|         :param params: Dict of :term:`report params`. |  | ||||||
| 
 |  | ||||||
|         :param progress: Optional progress indicator factory. |  | ||||||
| 
 |  | ||||||
|         :returns: Data dict with structure shown above. |  | ||||||
|         """ |  | ||||||
|         data = report.make_data(params or {}, progress=progress, **kwargs) |  | ||||||
|         if not isinstance(data, dict): |  | ||||||
|             data = {'data': data} |  | ||||||
|         data.setdefault('output_title', report.report_title) |  | ||||||
|         return data |  | ||||||
|  | @ -1 +0,0 @@ | ||||||
| # nb. some tests require this to be a true package |  | ||||||
|  | @ -491,22 +491,6 @@ app_title = WuttaTest | ||||||
|             result = self.app.render_error(error) |             result = self.app.render_error(error) | ||||||
|         self.assertEqual(result, "RuntimeError") |         self.assertEqual(result, "RuntimeError") | ||||||
| 
 | 
 | ||||||
|     def test_render_percent(self): |  | ||||||
| 
 |  | ||||||
|         # null |  | ||||||
|         self.assertEqual(self.app.render_percent(None), "") |  | ||||||
| 
 |  | ||||||
|         # typical |  | ||||||
|         self.assertEqual(self.app.render_percent(12.3419), '12.34 %') |  | ||||||
| 
 |  | ||||||
|         # more decimal places |  | ||||||
|         self.assertEqual(self.app.render_percent(12.3419, decimals=3), '12.342 %') |  | ||||||
|         self.assertEqual(self.app.render_percent(12.3419, decimals=4), '12.3419 %') |  | ||||||
| 
 |  | ||||||
|         # negative |  | ||||||
|         self.assertEqual(self.app.render_percent(-12.3419), '(12.34 %)') |  | ||||||
|         self.assertEqual(self.app.render_percent(-12.3419, decimals=3), '(12.342 %)') |  | ||||||
| 
 |  | ||||||
|     def test_render_quantity(self): |     def test_render_quantity(self): | ||||||
| 
 | 
 | ||||||
|         # null |         # null | ||||||
|  | @ -573,12 +557,6 @@ app_title = WuttaTest | ||||||
|         people = self.app.get_people_handler() |         people = self.app.get_people_handler() | ||||||
|         self.assertIsInstance(people, PeopleHandler) |         self.assertIsInstance(people, PeopleHandler) | ||||||
| 
 | 
 | ||||||
|     def test_get_report_handler(self): |  | ||||||
|         from wuttjamaican.reports import ReportHandler |  | ||||||
| 
 |  | ||||||
|         handler = self.app.get_report_handler() |  | ||||||
|         self.assertIsInstance(handler, ReportHandler) |  | ||||||
| 
 |  | ||||||
|     def test_send_email(self): |     def test_send_email(self): | ||||||
|         from wuttjamaican.email import EmailHandler |         from wuttjamaican.email import EmailHandler | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -151,7 +151,7 @@ class TestEmailHandler(ConfigTestCase): | ||||||
|             self.assertEqual(len(modules), 1) |             self.assertEqual(len(modules), 1) | ||||||
|             self.assertIs(modules[0], mod) |             self.assertIs(modules[0], mod) | ||||||
| 
 | 
 | ||||||
|         # provider may specify modules as string |         # provider may specify modules as list | ||||||
|         providers = { |         providers = { | ||||||
|             'wuttatest': MagicMock(email_modules='wuttjamaican.email'), |             'wuttatest': MagicMock(email_modules='wuttjamaican.email'), | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,111 +0,0 @@ | ||||||
| # -*- coding: utf-8; -*- |  | ||||||
| 
 |  | ||||||
| from unittest.mock import patch, MagicMock |  | ||||||
| 
 |  | ||||||
| from wuttjamaican import reports as mod |  | ||||||
| from wuttjamaican.testing import ConfigTestCase |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class MockFooReport(mod.Report): |  | ||||||
|     report_key = 'mock_foo' |  | ||||||
|     report_title = "MOCK Report" |  | ||||||
| 
 |  | ||||||
|     def make_data(self, params, **kwargs): |  | ||||||
|         return [ |  | ||||||
|             {'foo': 'bar'}, |  | ||||||
|         ] |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class TestReport(ConfigTestCase): |  | ||||||
| 
 |  | ||||||
|     def test_get_output_columns(self): |  | ||||||
|         report = mod.Report(self.config) |  | ||||||
|         self.assertRaises(NotImplementedError, report.get_output_columns) |  | ||||||
| 
 |  | ||||||
|     def test_make_data(self): |  | ||||||
|         report = mod.Report(self.config) |  | ||||||
|         self.assertRaises(NotImplementedError, report.make_data, {}) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class TestReportHandler(ConfigTestCase): |  | ||||||
| 
 |  | ||||||
|     def make_handler(self): |  | ||||||
|         return mod.ReportHandler(self.config) |  | ||||||
| 
 |  | ||||||
|     def test_get_report_modules(self): |  | ||||||
| 
 |  | ||||||
|         # no providers, no report modules |  | ||||||
|         with patch.object(self.app, 'providers', new={}): |  | ||||||
|             handler = self.make_handler() |  | ||||||
|             self.assertEqual(handler.get_report_modules(), []) |  | ||||||
| 
 |  | ||||||
|         # provider may specify modules as list |  | ||||||
|         providers = { |  | ||||||
|             'wuttatest': MagicMock(report_modules=['wuttjamaican.reports']), |  | ||||||
|         } |  | ||||||
|         with patch.object(self.app, 'providers', new=providers): |  | ||||||
|             handler = self.make_handler() |  | ||||||
|             modules = handler.get_report_modules() |  | ||||||
|             self.assertEqual(len(modules), 1) |  | ||||||
|             self.assertIs(modules[0], mod) |  | ||||||
| 
 |  | ||||||
|         # provider may specify modules as string |  | ||||||
|         providers = { |  | ||||||
|             'wuttatest': MagicMock(report_modules='wuttjamaican.reports'), |  | ||||||
|         } |  | ||||||
|         with patch.object(self.app, 'providers', new=providers): |  | ||||||
|             handler = self.make_handler() |  | ||||||
|             modules = handler.get_report_modules() |  | ||||||
|             self.assertEqual(len(modules), 1) |  | ||||||
|             self.assertIs(modules[0], mod) |  | ||||||
| 
 |  | ||||||
|     def test_get_reports(self): |  | ||||||
| 
 |  | ||||||
|         # no providers, no reports |  | ||||||
|         with patch.object(self.app, 'providers', new={}): |  | ||||||
|             handler = self.make_handler() |  | ||||||
|             self.assertEqual(handler.get_reports(), {}) |  | ||||||
| 
 |  | ||||||
|         # provider may define reports (via modules) |  | ||||||
|         providers = { |  | ||||||
|             'wuttatest': MagicMock(report_modules=['tests.test_reports']), |  | ||||||
|         } |  | ||||||
|         with patch.object(self.app, 'providers', new=providers): |  | ||||||
|             handler = self.make_handler() |  | ||||||
|             reports = handler.get_reports() |  | ||||||
|             self.assertEqual(len(reports), 1) |  | ||||||
|             self.assertIn('mock_foo', reports) |  | ||||||
| 
 |  | ||||||
|     def test_get_report(self): |  | ||||||
|         providers = { |  | ||||||
|             'wuttatest': MagicMock(report_modules=['tests.test_reports']), |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         with patch.object(self.app, 'providers', new=providers): |  | ||||||
|             handler = self.make_handler() |  | ||||||
| 
 |  | ||||||
|             # as instance |  | ||||||
|             report = handler.get_report('mock_foo') |  | ||||||
|             self.assertIsInstance(report, mod.Report) |  | ||||||
|             self.assertIsInstance(report, MockFooReport) |  | ||||||
| 
 |  | ||||||
|             # as class |  | ||||||
|             report = handler.get_report('mock_foo', instance=False) |  | ||||||
|             self.assertTrue(issubclass(report, mod.Report)) |  | ||||||
|             self.assertIs(report, MockFooReport) |  | ||||||
| 
 |  | ||||||
|     def test_make_report_data(self): |  | ||||||
|         providers = { |  | ||||||
|             'wuttatest': MagicMock(report_modules=['tests.test_reports']), |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         with patch.object(self.app, 'providers', new=providers): |  | ||||||
|             handler = self.make_handler() |  | ||||||
|             report = handler.get_report('mock_foo') |  | ||||||
| 
 |  | ||||||
|             data = handler.make_report_data(report) |  | ||||||
|             self.assertEqual(len(data), 2) |  | ||||||
|             self.assertIn('output_title', data) |  | ||||||
|             self.assertEqual(data['output_title'], "MOCK Report") |  | ||||||
|             self.assertIn('data', data) |  | ||||||
|             self.assertEqual(data['data'], [{'foo': 'bar'}]) |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue