From da0d62ca078172fced95a5fdfe40f997e419cc3b Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Wed, 4 Jun 2014 14:25:08 -0400 Subject: [PATCH 1/4] [#1745] remove lie about python 3 string formatting --- doc/contributing/python.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/contributing/python.rst b/doc/contributing/python.rst index 8dc6c1f5c17..227b3032c76 100644 --- a/doc/contributing/python.rst +++ b/doc/contributing/python.rst @@ -83,8 +83,7 @@ String formatting ------------------ Don't use the old ``%s`` style string formatting, e.g. ``"i am a %s" % sub``. -This kind of string formatting is not helpful for internationalization and is -going away in Python 3. +This kind of string formatting is not helpful for internationalization. Use the `new .format() method`_ instead, and give meaningful names to each replacement field, for example:: From 6b7601491049bfaa3c4b6aab842f520c27f1a2b7 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Wed, 4 Jun 2014 14:54:44 -0400 Subject: [PATCH 2/4] [#1745] nothing wrong with from module import name --- doc/contributing/python.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/contributing/python.rst b/doc/contributing/python.rst index 227b3032c76..2cb16eff656 100644 --- a/doc/contributing/python.rst +++ b/doc/contributing/python.rst @@ -36,15 +36,19 @@ We also use triple single-quotes for docstrings, see `Docstrings`_. Imports ------- -- Don't use ``from module import *`` or ``from module import name``. Instead +- Don't use ``from module import *``. Instead just ``import module`` and then access names with ``module.name``. See `Idioms and Anti-Idioms in Python`_. You can make long module names more concise by aliasing them:: - + import foo.bar.baz as baz - and then access it with ``baz`` in your code. + or by importing just the submodule:: + + from foo.bar import baz + + and then access it with ``baz`` in your code. - Make all imports at the start of the file, after the module docstring. Imports should be grouped in the following order: From 050c4fac4f8c3c23d32cd4cd676b13c8cca3ebd3 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Thu, 5 Jun 2014 14:04:55 -0400 Subject: [PATCH 3/4] [#1745] recommend from module import name --- ckan/tests/test_coding_standards.py | 163 ---------------------------- doc/contributing/python.rst | 19 ++-- 2 files changed, 10 insertions(+), 172 deletions(-) diff --git a/ckan/tests/test_coding_standards.py b/ckan/tests/test_coding_standards.py index bba9d3633dc..5981096bfb4 100644 --- a/ckan/tests/test_coding_standards.py +++ b/ckan/tests/test_coding_standards.py @@ -215,169 +215,6 @@ def test_bad(self): show_fails(msg, self.fails) -class TestImportFromCkan(object): - ''' Find files using from ckan import ... style imports ''' - - # Ckan import file exceptions - # - # These files contain lines like `from ckan import x` This should not be - # done except from ckan.common which is written specifically to share - # external functions. When files are fixed they should be removed from - # this list. - # - # The reason for this is to try to remove as many of the circular import - # issues that exist. - - CKAN_IMPORTS_BLACKLIST_FILES = [ - 'bin/ckan-hmg-breakdown.py', - 'bin/dump-ukgov.py', - 'ckan/config/middleware.py', - 'ckan/controllers/error.py', - 'ckan/controllers/storage.py', - 'ckan/lib/authenticator.py', - 'ckan/lib/munge.py', - 'ckan/lib/plugins.py', - 'ckan/lib/search/index.py', - 'ckan/lib/search/query.py', - 'ckan/lib/search/sql.py', - 'ckan/logic/action/__init__.py', - 'ckan/logic/action/create.py', - 'ckan/logic/auth/delete.py', - 'ckan/logic/auth/get.py', - 'ckan/logic/auth/update.py', - 'ckan/logic/schema.py', - 'ckan/logic/validators.py', - 'ckan/migration/versions/034_resource_group_table.py', - 'ckan/migration/versions/035_harvesting_doc_versioning.py', - 'ckan/model/test_user.py', - 'ckan/plugins/__init__.py', - 'ckan/tests/__init__.py', - 'ckan/tests/ckantestplugin/ckantestplugin/__init__.py', - 'ckan/tests/functional/api/base.py', - 'ckan/tests/functional/api/model/test_group.py', - 'ckan/tests/functional/api/model/test_licenses.py', - 'ckan/tests/functional/api/model/test_package.py', - 'ckan/tests/functional/api/model/test_ratings.py', - 'ckan/tests/functional/api/model/test_relationships.py', - 'ckan/tests/functional/api/model/test_revisions.py', - 'ckan/tests/functional/api/model/test_tag.py', - 'ckan/tests/functional/api/test_api.py', - 'ckan/tests/functional/api/test_follow.py', - 'ckan/tests/functional/api/test_misc.py', - 'ckan/tests/functional/api/test_package_search.py', - 'ckan/tests/functional/api/test_resource.py', - 'ckan/tests/functional/api/test_resource_search.py', - 'ckan/tests/functional/api/test_revision_search.py', - 'ckan/tests/functional/api/test_user.py', - 'ckan/tests/functional/api/test_util.py', - 'ckan/tests/functional/base.py', - 'ckan/tests/functional/test_activity.py', - 'ckan/tests/functional/test_admin.py', - 'ckan/tests/functional/test_cors.py', - 'ckan/tests/functional/test_follow.py', - 'ckan/tests/functional/test_group.py', - 'ckan/tests/functional/test_home.py', - 'ckan/tests/functional/test_package.py', - 'ckan/tests/functional/test_package_relationships.py', - 'ckan/tests/functional/test_pagination.py', - 'ckan/tests/functional/test_revision.py', - 'ckan/tests/functional/test_search.py', - 'ckan/tests/functional/test_storage.py', - 'ckan/tests/functional/test_tag.py', - 'ckan/tests/functional/test_tag_vocab.py', - 'ckan/tests/functional/test_upload.py', - 'ckan/tests/functional/test_user.py', - 'ckan/tests/lib/__init__.py', - 'ckan/tests/lib/test_alphabet_pagination.py', - 'ckan/tests/lib/test_cli.py', - 'ckan/tests/lib/test_dictization.py', - 'ckan/tests/lib/test_dictization_schema.py', - 'ckan/tests/lib/test_field_types.py', - 'ckan/tests/lib/test_hash.py', - 'ckan/tests/lib/test_helpers.py', - 'ckan/tests/lib/test_i18n.py', - 'ckan/tests/lib/test_mailer.py', - 'ckan/tests/lib/test_navl.py', - 'ckan/tests/lib/test_resource_search.py', - 'ckan/tests/lib/test_simple_search.py', - 'ckan/tests/lib/test_solr_package_search.py', - 'ckan/tests/lib/test_solr_package_search_synchronous_update.py', - 'ckan/tests/lib/test_solr_schema_version.py', - 'ckan/tests/lib/test_solr_search_index.py', - 'ckan/tests/lib/test_tag_search.py', - 'ckan/tests/logic/test_action.py', - 'ckan/tests/logic/test_auth.py', - 'ckan/tests/logic/test_tag.py', - 'ckan/tests/logic/test_validators.py', - 'ckan/tests/misc/test_mock_mail_server.py', - 'ckan/tests/misc/test_sync.py', - 'ckan/tests/mock_mail_server.py', - 'ckan/tests/mock_plugin.py', - 'ckan/tests/models/test_extras.py', - 'ckan/tests/models/test_group.py', - 'ckan/tests/models/test_license.py', - 'ckan/tests/models/test_misc.py', - 'ckan/tests/models/test_package.py', - 'ckan/tests/models/test_package_relationships.py', - 'ckan/tests/models/test_purge_revision.py', - 'ckan/tests/models/test_resource.py', - 'ckan/tests/models/test_revision.py', - 'ckan/tests/models/test_user.py', - 'ckan/tests/pylons_controller.py', - 'ckan/tests/schema/test_schema.py', - 'ckan/tests/test_dumper.py', - 'ckan/tests/test_plugins.py', - 'ckan/tests/test_wsgi_ckanclient.py', - 'ckan/websetup.py', - 'ckanext/multilingual/plugin.py', - 'ckanext/reclinepreview/tests/test_preview.py', - 'ckanext/stats/controller.py', - 'ckanext/stats/tests/__init__.py', - 'ckanext/stats/tests/test_stats_lib.py', - 'ckanext/stats/tests/test_stats_plugin.py', - 'ckanext/test_tag_vocab_plugin.py', - 'setup.py', - ] - fails = {} - passes = [] - done = False - - @classmethod - def setup(cls): - if not cls.done: - cls.process() - cls.done = True - - @classmethod - def process(cls): - blacklist = cls.CKAN_IMPORTS_BLACKLIST_FILES - re_bad_import = re.compile(r'^from\s.*\bckan\b(?!\.common).*\bimport') - for path, filename in process_directory(base_path): - f = open(path, 'r') - count = 1 - errors = [] - for line in f: - if re_bad_import.search(line): - errors.append('ln:%s \t%s' % (count, line[:-1])) - count += 1 - if errors and not filename in blacklist: - cls.fails[filename] = output_errors(filename, errors) - elif not errors and filename in blacklist: - cls.passes.append(filename) - - def test_import_good(self): - msg = 'The following files passed ckan import rules' - msg += '\nThey need removing from the test blacklist' - show_passing(msg, self.passes) - - def test_import_bad(self): - msg = ('The following files have ckan import issues that need' - 'resolving\nThese files contain lines like `from ckan import x`' - ' This should not be done except from ckan.common which is' - ' written specifically to share external functions.') - show_fails(msg, self.fails) - - class TestImportStar(object): ''' Find files using from xxx import * ''' diff --git a/doc/contributing/python.rst b/doc/contributing/python.rst index 2cb16eff656..97387118c54 100644 --- a/doc/contributing/python.rst +++ b/doc/contributing/python.rst @@ -36,19 +36,20 @@ We also use triple single-quotes for docstrings, see `Docstrings`_. Imports ------- -- Don't use ``from module import *``. Instead - just ``import module`` and then access names with ``module.name``. - See `Idioms and Anti-Idioms in Python`_. +- Don't use ``from module import *``. Instead list the names you + need explicitly:: - You can make long module names more concise by aliasing them:: + from module import name1, name2 - import foo.bar.baz as baz + Use parenthesis around the names if they are longer than one line:: - or by importing just the submodule:: + from module import (name1, name2, ... + name12, name13) - from foo.bar import baz - - and then access it with ``baz`` in your code. + Most of the current CKAN code base imports just the modules and + then accesses names with ``module.name``. This allows circular + imports in some cases and may still be necessary, but is not + recommended for new code. - Make all imports at the start of the file, after the module docstring. Imports should be grouped in the following order: From 88f0cf9c4347ec6062e6a0cc6f137fc5f3888fbd Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Wed, 11 Jun 2014 14:50:20 -0400 Subject: [PATCH 4/4] [#1745] add importing diagram to imports section of coding guidelines --- doc/contributing/python.rst | 17 +- doc/images/ckan_importing_diagram.graphml | 324 ++++++++++++++++++++++ doc/images/ckan_importing_diagram.png | Bin 0 -> 13781 bytes 3 files changed, 338 insertions(+), 3 deletions(-) create mode 100644 doc/images/ckan_importing_diagram.graphml create mode 100644 doc/images/ckan_importing_diagram.png diff --git a/doc/contributing/python.rst b/doc/contributing/python.rst index 97387118c54..ed9544cdfab 100644 --- a/doc/contributing/python.rst +++ b/doc/contributing/python.rst @@ -36,6 +36,18 @@ We also use triple single-quotes for docstrings, see `Docstrings`_. Imports ------- +- Avoid creating circular imports by only importing modules more + specialized than the one you are editing. + + CKAN often uses code imported into a data structure instead of + importing names directly. For example CKAN controllers only use + ``get_action`` to access logic functions. This allows + customization by CKAN plugins. + + .. image:: /images/ckan_importing_diagram.png + :alt: CKAN importing diagram, general modules import + more specific modules + - Don't use ``from module import *``. Instead list the names you need explicitly:: @@ -48,8 +60,8 @@ Imports Most of the current CKAN code base imports just the modules and then accesses names with ``module.name``. This allows circular - imports in some cases and may still be necessary, but is not - recommended for new code. + imports in some cases and may still be necessary for exsiting + code, but is not recommended for new code. - Make all imports at the start of the file, after the module docstring. Imports should be grouped in the following order: @@ -58,7 +70,6 @@ Imports 2. Third-party imports 3. CKAN imports -.. _Idioms and Anti-Idioms in Python: http://docs.python.org/2/howto/doanddont.html Logging ------- diff --git a/doc/images/ckan_importing_diagram.graphml b/doc/images/ckan_importing_diagram.graphml new file mode 100644 index 00000000000..b8fc5c8f9a6 --- /dev/null +++ b/doc/images/ckan_importing_diagram.graphml @@ -0,0 +1,324 @@ + + + + + + + + + + + + + + + + + + + + + + + + ckan.config + + + + + + + + + + + + + + + + + + ckan.controllers + + + + + + + + + + + + + + + + + + ckan.logic + + + + + + + + + + + + + + + + + + ckan.lib.dictization + + + + + + + + + + + + + + + + + + ckan.models + + + + + + + + + + + + + + + + + + sqlalchemy + + + + + + + + + + + + + + + + + + ckan.lib.base.render + + + + + + + + + + + + + + + + + + ckan.lib.search + + + + + + + + + + + + + + + + + + solr + + + + + + + + + + + + + + + + + + jinja2 + + + + + + + + + + + + + + + + + + via route mapper + + + + + + + + + + + + + + + + + + + via get_action + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + via jinja_env + + + + + + + + + + + + + + + + diff --git a/doc/images/ckan_importing_diagram.png b/doc/images/ckan_importing_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..25ec95d33cc8815dc47b3923027fde0eb9dbc4ef GIT binary patch literal 13781 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