We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
DB INIT error when using usernames with non-alphanumeric chars
**CKAN version**

Master 713716b0689bf848865f05acd328ded29f041c5a (2.9.something)

**Describe the bug**

DB usernames with non alphanum chars will terminate in error the command `ckan db init`.

**Steps to reproduce**

Create a pg user with for instance a "`@`" in it, then update `ckan.ini` with the proper URL, 
e.g.: user `ckan@ckan`:

    sqlalchemy.url = postgresql://ckan@ckan:ckan@localhost/ckan_at

Running `ckan run` will not raise connection error (it won't find table, eventually). 

Running `ckan db init` will terminate with the error:

    invalid interpolation syntax in 'postgresql://ckan%40ckan:ckan@localhost/ckan_at' at position 17

**Expected behavior**

The command should connect to the db without errors.

**Additional details**

At https://github.com/ckan/ckan/blob/713716b0689bf848865f05acd328ded29f041c5a/ckan/model/__init__.py#L268-L270


`self.metadata.bind.url` contains `postgresql://ckan%40ckan:ckan@localhost/ckan_at`

We could replace the line with 
```python
        alembic_config.set_main_option(
            "sqlalchemy.url", config.get("sqlalchemy.url")
        )
```



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ckan/model/__init__.py`
Content:
```
1 # encoding: utf-8
2 
3 import warnings
4 import logging
5 import os
6 import re
7 from time import sleep
8 from os.path import splitext
9 
10 from sqlalchemy import MetaData, __version__ as sqav, Table
11 from sqlalchemy.exc import ProgrammingError
12 
13 from alembic.command import (
14     upgrade as alembic_upgrade,
15     downgrade as alembic_downgrade,
16     current as alembic_current
17 )
18 from alembic.config import Config as AlembicConfig
19 
20 from ckan.model import meta
21 
22 from ckan.model.meta import (
23     Session,
24     engine_is_sqlite,
25     engine_is_pg,
26 )
27 from ckan.model.core import (
28     State,
29 )
30 from ckan.model.system import (
31     System,
32 )
33 from ckan.model.package import (
34     Package,
35     PackageMember,
36     PACKAGE_NAME_MIN_LENGTH,
37     PACKAGE_NAME_MAX_LENGTH,
38     PACKAGE_VERSION_MAX_LENGTH,
39     package_table,
40     package_member_table,
41 )
42 from ckan.model.tag import (
43     Tag,
44     PackageTag,
45     MAX_TAG_LENGTH,
46     MIN_TAG_LENGTH,
47     tag_table,
48     package_tag_table,
49 )
50 from ckan.model.user import (
51     User,
52     user_table,
53 )
54 from ckan.model.group import (
55     Member,
56     Group,
57     group_table,
58     member_table,
59 )
60 from ckan.model.group_extra import (
61     GroupExtra,
62     group_extra_table,
63 )
64 from ckan.model.package_extra import (
65     PackageExtra,
66     package_extra_table,
67 )
68 from ckan.model.resource import (
69     Resource,
70     DictProxy,
71     resource_table,
72 )
73 from ckan.model.resource_view import (
74     ResourceView,
75     resource_view_table,
76 )
77 from ckan.model.tracking import (
78     tracking_summary_table,
79     TrackingSummary,
80     tracking_raw_table
81 )
82 from ckan.model.rating import (
83     Rating,
84     MIN_RATING,
85     MAX_RATING,
86 )
87 from ckan.model.package_relationship import (
88     PackageRelationship,
89     package_relationship_table,
90 )
91 from ckan.model.task_status import (
92     TaskStatus,
93     task_status_table,
94 )
95 from ckan.model.vocabulary import (
96     Vocabulary,
97     VOCABULARY_NAME_MAX_LENGTH,
98     VOCABULARY_NAME_MIN_LENGTH,
99 )
100 from ckan.model.activity import (
101     Activity,
102     ActivityDetail,
103     activity_table,
104     activity_detail_table,
105 )
106 from ckan.model.term_translation import (
107     term_translation_table,
108 )
109 from ckan.model.follower import (
110     UserFollowingUser,
111     UserFollowingDataset,
112     UserFollowingGroup,
113 )
114 from ckan.model.system_info import (
115     system_info_table,
116     SystemInfo,
117     get_system_info,
118     set_system_info,
119     delete_system_info,
120 )
121 from ckan.model.domain_object import (
122     DomainObjectOperation,
123     DomainObject,
124 )
125 from ckan.model.dashboard import (
126     Dashboard,
127 )
128 from ckan.model.api_token import (
129     ApiToken,
130 )
131 
132 import ckan.migration
133 from ckan.common import config
134 
135 
136 log = logging.getLogger(__name__)
137 
138 DB_CONNECT_RETRIES = 10
139 
140 
141 def init_model(engine):
142     '''Call me before using any of the tables or classes in the model'''
143     meta.Session.remove()
144     meta.Session.configure(bind=engine)
145     meta.create_local_session.configure(bind=engine)
146     meta.engine = engine
147     meta.metadata.bind = engine
148     # sqlalchemy migrate version table
149     import sqlalchemy.exc
150     for i in reversed(range(DB_CONNECT_RETRIES)):
151         try:
152             Table('alembic_version', meta.metadata, autoload=True)
153             break
154         except sqlalchemy.exc.NoSuchTableError:
155             break
156         except sqlalchemy.exc.OperationalError as e:
157             if 'database system is starting up' in repr(e.orig) and i:
158                 sleep(DB_CONNECT_RETRIES - i)
159                 continue
160             raise
161 
162 
163 class Repository():
164     _alembic_ini = os.path.join(
165         os.path.dirname(ckan.migration.__file__),
166         u"alembic.ini"
167     )
168 
169     # note: tables_created value is not sustained between instantiations
170     #       so only useful for tests. The alternative is to use
171     #       are_tables_created().
172     tables_created_and_initialised = False
173 
174     def __init__(self, metadata, session):
175         self.metadata = metadata
176         self.session = session
177         self.commit = session.commit
178 
179     def commit_and_remove(self):
180         self.session.commit()
181         self.session.remove()
182 
183     def init_db(self):
184         '''Ensures tables, const data and some default config is created.
185         This method MUST be run before using CKAN for the first time.
186         Before this method is run, you can either have a clean db or tables
187         that may have been setup with either upgrade_db or a previous run of
188         init_db.
189         '''
190         warnings.filterwarnings('ignore', 'SAWarning')
191         self.session.rollback()
192         self.session.remove()
193         # sqlite database needs to be recreated each time as the
194         # memory database is lost.
195 
196         if self.metadata.bind.engine.url.drivername == 'sqlite':
197             # this creates the tables, which isn't required inbetween tests
198             # that have simply called rebuild_db.
199             self.create_db()
200         else:
201             if not self.tables_created_and_initialised:
202                 self.upgrade_db()
203                 self.tables_created_and_initialised = True
204         log.info('Database initialised')
205 
206     def clean_db(self):
207         self.commit_and_remove()
208         meta.metadata = MetaData(self.metadata.bind)
209         with warnings.catch_warnings():
210             warnings.filterwarnings('ignore', '.*(reflection|tsvector).*')
211             meta.metadata.reflect()
212 
213         meta.metadata.drop_all()
214         self.tables_created_and_initialised = False
215         log.info('Database tables dropped')
216 
217     def create_db(self):
218         '''Ensures tables, const data and some default config is created.
219         i.e. the same as init_db APART from when running tests, when init_db
220         has shortcuts.
221         '''
222         self.metadata.create_all(bind=self.metadata.bind)
223         log.info('Database tables created')
224 
225     def rebuild_db(self):
226         '''Clean and init the db'''
227         if self.tables_created_and_initialised:
228             # just delete data, leaving tables - this is faster
229             self.delete_all()
230         else:
231             # delete tables and data
232             self.clean_db()
233         self.session.remove()
234         self.init_db()
235         self.session.flush()
236         log.info('Database rebuilt')
237 
238     def delete_all(self):
239         '''Delete all data from all tables.'''
240         self.session.remove()
241         ## use raw connection for performance
242         connection = self.session.connection()
243         if sqav.startswith("0.4"):
244             tables = self.metadata.table_iterator()
245         else:
246             tables = reversed(self.metadata.sorted_tables)
247         for table in tables:
248             if table.name == 'alembic_version':
249                 continue
250             connection.execute('delete from "%s"' % table.name)
251         self.session.commit()
252         log.info('Database table data deleted')
253 
254     def reset_alembic_output(self):
255         self._alembic_output = []
256 
257     def add_alembic_output(self, *args):
258         self._alembic_output.append(args)
259 
260     def take_alembic_output(self, with_reset=True):
261         output = self._alembic_output
262         self._alembic_config = []
263         return output
264 
265     def setup_migration_version_control(self):
266         self.reset_alembic_output()
267         alembic_config = AlembicConfig(self._alembic_ini)
268         alembic_config.set_main_option(
269             "sqlalchemy.url", str(self.metadata.bind.url)
270         )
271         try:
272             sqlalchemy_migrate_version = self.metadata.bind.execute(
273                 u'select version from migrate_version'
274             ).scalar()
275         except ProgrammingError:
276             sqlalchemy_migrate_version = 0
277 
278         # this value is used for graceful upgrade from
279         # sqlalchemy-migrate to alembic
280         alembic_config.set_main_option(
281             "sqlalchemy_migrate_version", str(sqlalchemy_migrate_version)
282         )
283         # This is an interceptor for alembic output. Otherwise,
284         # everything will be printed to stdout
285         alembic_config.print_stdout = self.add_alembic_output
286 
287         self.alembic_config = alembic_config
288 
289     def current_version(self):
290         try:
291             alembic_current(self.alembic_config)
292             return self.take_alembic_output()[0][0]
293         except (TypeError, IndexError):
294             # alembic is not initialized yet
295             return 'base'
296 
297     def downgrade_db(self, version='base'):
298         self.setup_migration_version_control()
299         alembic_downgrade(self.alembic_config, version)
300         log.info(u'CKAN database version set to: %s', version)
301 
302     def upgrade_db(self, version='head'):
303         '''Upgrade db using sqlalchemy migrations.
304 
305         @param version: version to upgrade to (if None upgrade to latest)
306         '''
307         _assert_engine_msg = (
308             u'Database migration - only Postgresql engine supported (not %s).'
309         ) % meta.engine.name
310         assert meta.engine.name in (
311             u'postgres', u'postgresql'
312         ), _assert_engine_msg
313         self.setup_migration_version_control()
314         version_before = self.current_version()
315         alembic_upgrade(self.alembic_config, version)
316         version_after = self.current_version()
317 
318         if version_after != version_before:
319             log.info(
320                 u'CKAN database version upgraded: %s -> %s',
321                 version_before,
322                 version_after
323             )
324         else:
325             log.info(u'CKAN database version remains as: %s', version_after)
326 
327     def are_tables_created(self):
328         meta.metadata = MetaData(self.metadata.bind)
329         with warnings.catch_warnings():
330             warnings.filterwarnings('ignore', '.*(reflection|geometry).*')
331             meta.metadata.reflect()
332         return bool(meta.metadata.tables)
333 
334 
335 repo = Repository(meta.metadata, meta.Session)
336 
337 
338 def is_id(id_string):
339     '''Tells the client if the string looks like a revision id or not'''
340     reg_ex = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
341     return bool(re.match(reg_ex, id_string))
342 
343 
344 def parse_db_config(config_key=u'sqlalchemy.url'):
345     u''' Takes a config key for a database connection url and parses it into
346     a dictionary. Expects a url like:
347 
348     'postgres://tester:pass@localhost/ckantest3'
349 
350     Returns None if the url could not be parsed.
351     '''
352     url = config[config_key]
353     regex = [
354         u'^\\s*(?P<db_type>\\w*)', u'://', u'(?P<db_user>[^:]*)', u':?',
355         u'(?P<db_pass>[^@]*)', u'@', u'(?P<db_host>[^/:]*)', u':?',
356         u'(?P<db_port>[^/]*)', u'/', u'(?P<db_name>[\\w.-]*)'
357     ]
358     db_details_match = re.match(u''.join(regex), url)
359     if not db_details_match:
360         return
361     return db_details_match.groupdict()
362
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ckan/model/__init__.py b/ckan/model/__init__.py
--- a/ckan/model/__init__.py
+++ b/ckan/model/__init__.py
@@ -266,7 +266,7 @@
         self.reset_alembic_output()
         alembic_config = AlembicConfig(self._alembic_ini)
         alembic_config.set_main_option(
-            "sqlalchemy.url", str(self.metadata.bind.url)
+            "sqlalchemy.url", config.get("sqlalchemy.url")
         )
         try:
             sqlalchemy_migrate_version = self.metadata.bind.execute(

{"golden_diff": "diff --git a/ckan/model/__init__.py b/ckan/model/__init__.py\n--- a/ckan/model/__init__.py\n+++ b/ckan/model/__init__.py\n@@ -266,7 +266,7 @@\n         self.reset_alembic_output()\n         alembic_config = AlembicConfig(self._alembic_ini)\n         alembic_config.set_main_option(\n-            \"sqlalchemy.url\", str(self.metadata.bind.url)\n+            \"sqlalchemy.url\", config.get(\"sqlalchemy.url\")\n         )\n         try:\n             sqlalchemy_migrate_version = self.metadata.bind.execute(\n", "issue": "DB INIT error when using usernames with non-alphanumeric chars\n**CKAN version**\r\n\r\nMaster 713716b0689bf848865f05acd328ded29f041c5a (2.9.something)\r\n\r\n**Describe the bug**\r\n\r\nDB usernames with non alphanum chars will terminate in error the command `ckan db init`.\r\n\r\n**Steps to reproduce**\r\n\r\nCreate a pg user with for instance a \"`@`\" in it, then update `ckan.ini` with the proper URL, \r\ne.g.: user `ckan@ckan`:\r\n\r\n    sqlalchemy.url = postgresql://ckan@ckan:ckan@localhost/ckan_at\r\n\r\nRunning `ckan run` will not raise connection error (it won't find table, eventually). \r\n\r\nRunning `ckan db init` will terminate with the error:\r\n\r\n    invalid interpolation syntax in 'postgresql://ckan%40ckan:ckan@localhost/ckan_at' at position 17\r\n\r\n**Expected behavior**\r\n\r\nThe command should connect to the db without errors.\r\n\r\n**Additional details**\r\n\r\nAt https://github.com/ckan/ckan/blob/713716b0689bf848865f05acd328ded29f041c5a/ckan/model/__init__.py#L268-L270\r\n\r\n\r\n`self.metadata.bind.url` contains `postgresql://ckan%40ckan:ckan@localhost/ckan_at`\r\n\r\nWe could replace the line with \r\n```python\r\n        alembic_config.set_main_option(\r\n            \"sqlalchemy.url\", config.get(\"sqlalchemy.url\")\r\n        )\r\n```\r\n\r\n\n", "before_files": [{"content": "# encoding: utf-8\n\nimport warnings\nimport logging\nimport os\nimport re\nfrom time import sleep\nfrom os.path import splitext\n\nfrom sqlalchemy import MetaData, __version__ as sqav, Table\nfrom sqlalchemy.exc import ProgrammingError\n\nfrom alembic.command import (\n    upgrade as alembic_upgrade,\n    downgrade as alembic_downgrade,\n    current as alembic_current\n)\nfrom alembic.config import Config as AlembicConfig\n\nfrom ckan.model import meta\n\nfrom ckan.model.meta import (\n    Session,\n    engine_is_sqlite,\n    engine_is_pg,\n)\nfrom ckan.model.core import (\n    State,\n)\nfrom ckan.model.system import (\n    System,\n)\nfrom ckan.model.package import (\n    Package,\n    PackageMember,\n    PACKAGE_NAME_MIN_LENGTH,\n    PACKAGE_NAME_MAX_LENGTH,\n    PACKAGE_VERSION_MAX_LENGTH,\n    package_table,\n    package_member_table,\n)\nfrom ckan.model.tag import (\n    Tag,\n    PackageTag,\n    MAX_TAG_LENGTH,\n    MIN_TAG_LENGTH,\n    tag_table,\n    package_tag_table,\n)\nfrom ckan.model.user import (\n    User,\n    user_table,\n)\nfrom ckan.model.group import (\n    Member,\n    Group,\n    group_table,\n    member_table,\n)\nfrom ckan.model.group_extra import (\n    GroupExtra,\n    group_extra_table,\n)\nfrom ckan.model.package_extra import (\n    PackageExtra,\n    package_extra_table,\n)\nfrom ckan.model.resource import (\n    Resource,\n    DictProxy,\n    resource_table,\n)\nfrom ckan.model.resource_view import (\n    ResourceView,\n    resource_view_table,\n)\nfrom ckan.model.tracking import (\n    tracking_summary_table,\n    TrackingSummary,\n    tracking_raw_table\n)\nfrom ckan.model.rating import (\n    Rating,\n    MIN_RATING,\n    MAX_RATING,\n)\nfrom ckan.model.package_relationship import (\n    PackageRelationship,\n    package_relationship_table,\n)\nfrom ckan.model.task_status import (\n    TaskStatus,\n    task_status_table,\n)\nfrom ckan.model.vocabulary import (\n    Vocabulary,\n    VOCABULARY_NAME_MAX_LENGTH,\n    VOCABULARY_NAME_MIN_LENGTH,\n)\nfrom ckan.model.activity import (\n    Activity,\n    ActivityDetail,\n    activity_table,\n    activity_detail_table,\n)\nfrom ckan.model.term_translation import (\n    term_translation_table,\n)\nfrom ckan.model.follower import (\n    UserFollowingUser,\n    UserFollowingDataset,\n    UserFollowingGroup,\n)\nfrom ckan.model.system_info import (\n    system_info_table,\n    SystemInfo,\n    get_system_info,\n    set_system_info,\n    delete_system_info,\n)\nfrom ckan.model.domain_object import (\n    DomainObjectOperation,\n    DomainObject,\n)\nfrom ckan.model.dashboard import (\n    Dashboard,\n)\nfrom ckan.model.api_token import (\n    ApiToken,\n)\n\nimport ckan.migration\nfrom ckan.common import config\n\n\nlog = logging.getLogger(__name__)\n\nDB_CONNECT_RETRIES = 10\n\n\ndef init_model(engine):\n    '''Call me before using any of the tables or classes in the model'''\n    meta.Session.remove()\n    meta.Session.configure(bind=engine)\n    meta.create_local_session.configure(bind=engine)\n    meta.engine = engine\n    meta.metadata.bind = engine\n    # sqlalchemy migrate version table\n    import sqlalchemy.exc\n    for i in reversed(range(DB_CONNECT_RETRIES)):\n        try:\n            Table('alembic_version', meta.metadata, autoload=True)\n            break\n        except sqlalchemy.exc.NoSuchTableError:\n            break\n        except sqlalchemy.exc.OperationalError as e:\n            if 'database system is starting up' in repr(e.orig) and i:\n                sleep(DB_CONNECT_RETRIES - i)\n                continue\n            raise\n\n\nclass Repository():\n    _alembic_ini = os.path.join(\n        os.path.dirname(ckan.migration.__file__),\n        u\"alembic.ini\"\n    )\n\n    # note: tables_created value is not sustained between instantiations\n    #       so only useful for tests. The alternative is to use\n    #       are_tables_created().\n    tables_created_and_initialised = False\n\n    def __init__(self, metadata, session):\n        self.metadata = metadata\n        self.session = session\n        self.commit = session.commit\n\n    def commit_and_remove(self):\n        self.session.commit()\n        self.session.remove()\n\n    def init_db(self):\n        '''Ensures tables, const data and some default config is created.\n        This method MUST be run before using CKAN for the first time.\n        Before this method is run, you can either have a clean db or tables\n        that may have been setup with either upgrade_db or a previous run of\n        init_db.\n        '''\n        warnings.filterwarnings('ignore', 'SAWarning')\n        self.session.rollback()\n        self.session.remove()\n        # sqlite database needs to be recreated each time as the\n        # memory database is lost.\n\n        if self.metadata.bind.engine.url.drivername == 'sqlite':\n            # this creates the tables, which isn't required inbetween tests\n            # that have simply called rebuild_db.\n            self.create_db()\n        else:\n            if not self.tables_created_and_initialised:\n                self.upgrade_db()\n                self.tables_created_and_initialised = True\n        log.info('Database initialised')\n\n    def clean_db(self):\n        self.commit_and_remove()\n        meta.metadata = MetaData(self.metadata.bind)\n        with warnings.catch_warnings():\n            warnings.filterwarnings('ignore', '.*(reflection|tsvector).*')\n            meta.metadata.reflect()\n\n        meta.metadata.drop_all()\n        self.tables_created_and_initialised = False\n        log.info('Database tables dropped')\n\n    def create_db(self):\n        '''Ensures tables, const data and some default config is created.\n        i.e. the same as init_db APART from when running tests, when init_db\n        has shortcuts.\n        '''\n        self.metadata.create_all(bind=self.metadata.bind)\n        log.info('Database tables created')\n\n    def rebuild_db(self):\n        '''Clean and init the db'''\n        if self.tables_created_and_initialised:\n            # just delete data, leaving tables - this is faster\n            self.delete_all()\n        else:\n            # delete tables and data\n            self.clean_db()\n        self.session.remove()\n        self.init_db()\n        self.session.flush()\n        log.info('Database rebuilt')\n\n    def delete_all(self):\n        '''Delete all data from all tables.'''\n        self.session.remove()\n        ## use raw connection for performance\n        connection = self.session.connection()\n        if sqav.startswith(\"0.4\"):\n            tables = self.metadata.table_iterator()\n        else:\n            tables = reversed(self.metadata.sorted_tables)\n        for table in tables:\n            if table.name == 'alembic_version':\n                continue\n            connection.execute('delete from \"%s\"' % table.name)\n        self.session.commit()\n        log.info('Database table data deleted')\n\n    def reset_alembic_output(self):\n        self._alembic_output = []\n\n    def add_alembic_output(self, *args):\n        self._alembic_output.append(args)\n\n    def take_alembic_output(self, with_reset=True):\n        output = self._alembic_output\n        self._alembic_config = []\n        return output\n\n    def setup_migration_version_control(self):\n        self.reset_alembic_output()\n        alembic_config = AlembicConfig(self._alembic_ini)\n        alembic_config.set_main_option(\n            \"sqlalchemy.url\", str(self.metadata.bind.url)\n        )\n        try:\n            sqlalchemy_migrate_version = self.metadata.bind.execute(\n                u'select version from migrate_version'\n            ).scalar()\n        except ProgrammingError:\n            sqlalchemy_migrate_version = 0\n\n        # this value is used for graceful upgrade from\n        # sqlalchemy-migrate to alembic\n        alembic_config.set_main_option(\n            \"sqlalchemy_migrate_version\", str(sqlalchemy_migrate_version)\n        )\n        # This is an interceptor for alembic output. Otherwise,\n        # everything will be printed to stdout\n        alembic_config.print_stdout = self.add_alembic_output\n\n        self.alembic_config = alembic_config\n\n    def current_version(self):\n        try:\n            alembic_current(self.alembic_config)\n            return self.take_alembic_output()[0][0]\n        except (TypeError, IndexError):\n            # alembic is not initialized yet\n            return 'base'\n\n    def downgrade_db(self, version='base'):\n        self.setup_migration_version_control()\n        alembic_downgrade(self.alembic_config, version)\n        log.info(u'CKAN database version set to: %s', version)\n\n    def upgrade_db(self, version='head'):\n        '''Upgrade db using sqlalchemy migrations.\n\n        @param version: version to upgrade to (if None upgrade to latest)\n        '''\n        _assert_engine_msg = (\n            u'Database migration - only Postgresql engine supported (not %s).'\n        ) % meta.engine.name\n        assert meta.engine.name in (\n            u'postgres', u'postgresql'\n        ), _assert_engine_msg\n        self.setup_migration_version_control()\n        version_before = self.current_version()\n        alembic_upgrade(self.alembic_config, version)\n        version_after = self.current_version()\n\n        if version_after != version_before:\n            log.info(\n                u'CKAN database version upgraded: %s -> %s',\n                version_before,\n                version_after\n            )\n        else:\n            log.info(u'CKAN database version remains as: %s', version_after)\n\n    def are_tables_created(self):\n        meta.metadata = MetaData(self.metadata.bind)\n        with warnings.catch_warnings():\n            warnings.filterwarnings('ignore', '.*(reflection|geometry).*')\n            meta.metadata.reflect()\n        return bool(meta.metadata.tables)\n\n\nrepo = Repository(meta.metadata, meta.Session)\n\n\ndef is_id(id_string):\n    '''Tells the client if the string looks like a revision id or not'''\n    reg_ex = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'\n    return bool(re.match(reg_ex, id_string))\n\n\ndef parse_db_config(config_key=u'sqlalchemy.url'):\n    u''' Takes a config key for a database connection url and parses it into\n    a dictionary. Expects a url like:\n\n    'postgres://tester:pass@localhost/ckantest3'\n\n    Returns None if the url could not be parsed.\n    '''\n    url = config[config_key]\n    regex = [\n        u'^\\\\s*(?P<db_type>\\\\w*)', u'://', u'(?P<db_user>[^:]*)', u':?',\n        u'(?P<db_pass>[^@]*)', u'@', u'(?P<db_host>[^/:]*)', u':?',\n        u'(?P<db_port>[^/]*)', u'/', u'(?P<db_name>[\\\\w.-]*)'\n    ]\n    db_details_match = re.match(u''.join(regex), url)\n    if not db_details_match:\n        return\n    return db_details_match.groupdict()\n", "path": "ckan/model/__init__.py"}], "after_files": [{"content": "# encoding: utf-8\n\nimport warnings\nimport logging\nimport os\nimport re\nfrom time import sleep\nfrom os.path import splitext\n\nfrom sqlalchemy import MetaData, __version__ as sqav, Table\nfrom sqlalchemy.exc import ProgrammingError\n\nfrom alembic.command import (\n    upgrade as alembic_upgrade,\n    downgrade as alembic_downgrade,\n    current as alembic_current\n)\nfrom alembic.config import Config as AlembicConfig\n\nfrom ckan.model import meta\n\nfrom ckan.model.meta import (\n    Session,\n    engine_is_sqlite,\n    engine_is_pg,\n)\nfrom ckan.model.core import (\n    State,\n)\nfrom ckan.model.system import (\n    System,\n)\nfrom ckan.model.package import (\n    Package,\n    PackageMember,\n    PACKAGE_NAME_MIN_LENGTH,\n    PACKAGE_NAME_MAX_LENGTH,\n    PACKAGE_VERSION_MAX_LENGTH,\n    package_table,\n    package_member_table,\n)\nfrom ckan.model.tag import (\n    Tag,\n    PackageTag,\n    MAX_TAG_LENGTH,\n    MIN_TAG_LENGTH,\n    tag_table,\n    package_tag_table,\n)\nfrom ckan.model.user import (\n    User,\n    user_table,\n)\nfrom ckan.model.group import (\n    Member,\n    Group,\n    group_table,\n    member_table,\n)\nfrom ckan.model.group_extra import (\n    GroupExtra,\n    group_extra_table,\n)\nfrom ckan.model.package_extra import (\n    PackageExtra,\n    package_extra_table,\n)\nfrom ckan.model.resource import (\n    Resource,\n    DictProxy,\n    resource_table,\n)\nfrom ckan.model.resource_view import (\n    ResourceView,\n    resource_view_table,\n)\nfrom ckan.model.tracking import (\n    tracking_summary_table,\n    TrackingSummary,\n    tracking_raw_table\n)\nfrom ckan.model.rating import (\n    Rating,\n    MIN_RATING,\n    MAX_RATING,\n)\nfrom ckan.model.package_relationship import (\n    PackageRelationship,\n    package_relationship_table,\n)\nfrom ckan.model.task_status import (\n    TaskStatus,\n    task_status_table,\n)\nfrom ckan.model.vocabulary import (\n    Vocabulary,\n    VOCABULARY_NAME_MAX_LENGTH,\n    VOCABULARY_NAME_MIN_LENGTH,\n)\nfrom ckan.model.activity import (\n    Activity,\n    ActivityDetail,\n    activity_table,\n    activity_detail_table,\n)\nfrom ckan.model.term_translation import (\n    term_translation_table,\n)\nfrom ckan.model.follower import (\n    UserFollowingUser,\n    UserFollowingDataset,\n    UserFollowingGroup,\n)\nfrom ckan.model.system_info import (\n    system_info_table,\n    SystemInfo,\n    get_system_info,\n    set_system_info,\n    delete_system_info,\n)\nfrom ckan.model.domain_object import (\n    DomainObjectOperation,\n    DomainObject,\n)\nfrom ckan.model.dashboard import (\n    Dashboard,\n)\nfrom ckan.model.api_token import (\n    ApiToken,\n)\n\nimport ckan.migration\nfrom ckan.common import config\n\n\nlog = logging.getLogger(__name__)\n\nDB_CONNECT_RETRIES = 10\n\n\ndef init_model(engine):\n    '''Call me before using any of the tables or classes in the model'''\n    meta.Session.remove()\n    meta.Session.configure(bind=engine)\n    meta.create_local_session.configure(bind=engine)\n    meta.engine = engine\n    meta.metadata.bind = engine\n    # sqlalchemy migrate version table\n    import sqlalchemy.exc\n    for i in reversed(range(DB_CONNECT_RETRIES)):\n        try:\n            Table('alembic_version', meta.metadata, autoload=True)\n            break\n        except sqlalchemy.exc.NoSuchTableError:\n            break\n        except sqlalchemy.exc.OperationalError as e:\n            if 'database system is starting up' in repr(e.orig) and i:\n                sleep(DB_CONNECT_RETRIES - i)\n                continue\n            raise\n\n\nclass Repository():\n    _alembic_ini = os.path.join(\n        os.path.dirname(ckan.migration.__file__),\n        u\"alembic.ini\"\n    )\n\n    # note: tables_created value is not sustained between instantiations\n    #       so only useful for tests. The alternative is to use\n    #       are_tables_created().\n    tables_created_and_initialised = False\n\n    def __init__(self, metadata, session):\n        self.metadata = metadata\n        self.session = session\n        self.commit = session.commit\n\n    def commit_and_remove(self):\n        self.session.commit()\n        self.session.remove()\n\n    def init_db(self):\n        '''Ensures tables, const data and some default config is created.\n        This method MUST be run before using CKAN for the first time.\n        Before this method is run, you can either have a clean db or tables\n        that may have been setup with either upgrade_db or a previous run of\n        init_db.\n        '''\n        warnings.filterwarnings('ignore', 'SAWarning')\n        self.session.rollback()\n        self.session.remove()\n        # sqlite database needs to be recreated each time as the\n        # memory database is lost.\n\n        if self.metadata.bind.engine.url.drivername == 'sqlite':\n            # this creates the tables, which isn't required inbetween tests\n            # that have simply called rebuild_db.\n            self.create_db()\n        else:\n            if not self.tables_created_and_initialised:\n                self.upgrade_db()\n                self.tables_created_and_initialised = True\n        log.info('Database initialised')\n\n    def clean_db(self):\n        self.commit_and_remove()\n        meta.metadata = MetaData(self.metadata.bind)\n        with warnings.catch_warnings():\n            warnings.filterwarnings('ignore', '.*(reflection|tsvector).*')\n            meta.metadata.reflect()\n\n        meta.metadata.drop_all()\n        self.tables_created_and_initialised = False\n        log.info('Database tables dropped')\n\n    def create_db(self):\n        '''Ensures tables, const data and some default config is created.\n        i.e. the same as init_db APART from when running tests, when init_db\n        has shortcuts.\n        '''\n        self.metadata.create_all(bind=self.metadata.bind)\n        log.info('Database tables created')\n\n    def rebuild_db(self):\n        '''Clean and init the db'''\n        if self.tables_created_and_initialised:\n            # just delete data, leaving tables - this is faster\n            self.delete_all()\n        else:\n            # delete tables and data\n            self.clean_db()\n        self.session.remove()\n        self.init_db()\n        self.session.flush()\n        log.info('Database rebuilt')\n\n    def delete_all(self):\n        '''Delete all data from all tables.'''\n        self.session.remove()\n        ## use raw connection for performance\n        connection = self.session.connection()\n        if sqav.startswith(\"0.4\"):\n            tables = self.metadata.table_iterator()\n        else:\n            tables = reversed(self.metadata.sorted_tables)\n        for table in tables:\n            if table.name == 'alembic_version':\n                continue\n            connection.execute('delete from \"%s\"' % table.name)\n        self.session.commit()\n        log.info('Database table data deleted')\n\n    def reset_alembic_output(self):\n        self._alembic_output = []\n\n    def add_alembic_output(self, *args):\n        self._alembic_output.append(args)\n\n    def take_alembic_output(self, with_reset=True):\n        output = self._alembic_output\n        self._alembic_config = []\n        return output\n\n    def setup_migration_version_control(self):\n        self.reset_alembic_output()\n        alembic_config = AlembicConfig(self._alembic_ini)\n        alembic_config.set_main_option(\n            \"sqlalchemy.url\", config.get(\"sqlalchemy.url\")\n        )\n        try:\n            sqlalchemy_migrate_version = self.metadata.bind.execute(\n                u'select version from migrate_version'\n            ).scalar()\n        except ProgrammingError:\n            sqlalchemy_migrate_version = 0\n\n        # this value is used for graceful upgrade from\n        # sqlalchemy-migrate to alembic\n        alembic_config.set_main_option(\n            \"sqlalchemy_migrate_version\", str(sqlalchemy_migrate_version)\n        )\n        # This is an interceptor for alembic output. Otherwise,\n        # everything will be printed to stdout\n        alembic_config.print_stdout = self.add_alembic_output\n\n        self.alembic_config = alembic_config\n\n    def current_version(self):\n        try:\n            alembic_current(self.alembic_config)\n            return self.take_alembic_output()[0][0]\n        except (TypeError, IndexError):\n            # alembic is not initialized yet\n            return 'base'\n\n    def downgrade_db(self, version='base'):\n        self.setup_migration_version_control()\n        alembic_downgrade(self.alembic_config, version)\n        log.info(u'CKAN database version set to: %s', version)\n\n    def upgrade_db(self, version='head'):\n        '''Upgrade db using sqlalchemy migrations.\n\n        @param version: version to upgrade to (if None upgrade to latest)\n        '''\n        _assert_engine_msg = (\n            u'Database migration - only Postgresql engine supported (not %s).'\n        ) % meta.engine.name\n        assert meta.engine.name in (\n            u'postgres', u'postgresql'\n        ), _assert_engine_msg\n        self.setup_migration_version_control()\n        version_before = self.current_version()\n        alembic_upgrade(self.alembic_config, version)\n        version_after = self.current_version()\n\n        if version_after != version_before:\n            log.info(\n                u'CKAN database version upgraded: %s -> %s',\n                version_before,\n                version_after\n            )\n        else:\n            log.info(u'CKAN database version remains as: %s', version_after)\n\n    def are_tables_created(self):\n        meta.metadata = MetaData(self.metadata.bind)\n        with warnings.catch_warnings():\n            warnings.filterwarnings('ignore', '.*(reflection|geometry).*')\n            meta.metadata.reflect()\n        return bool(meta.metadata.tables)\n\n\nrepo = Repository(meta.metadata, meta.Session)\n\n\ndef is_id(id_string):\n    '''Tells the client if the string looks like a revision id or not'''\n    reg_ex = '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'\n    return bool(re.match(reg_ex, id_string))\n\n\ndef parse_db_config(config_key=u'sqlalchemy.url'):\n    u''' Takes a config key for a database connection url and parses it into\n    a dictionary. Expects a url like:\n\n    'postgres://tester:pass@localhost/ckantest3'\n\n    Returns None if the url could not be parsed.\n    '''\n    url = config[config_key]\n    regex = [\n        u'^\\\\s*(?P<db_type>\\\\w*)', u'://', u'(?P<db_user>[^:]*)', u':?',\n        u'(?P<db_pass>[^@]*)', u'@', u'(?P<db_host>[^/:]*)', u':?',\n        u'(?P<db_port>[^/]*)', u'/', u'(?P<db_name>[\\\\w.-]*)'\n    ]\n    db_details_match = re.match(u''.join(regex), url)\n    if not db_details_match:\n        return\n    return db_details_match.groupdict()\n", "path": "ckan/model/__init__.py"}]}