##// END OF EJS Templates
docs: describe, visualize, and verify internal code structure and layering...
Mads Kiilerich -
r8550:52816813 default
parent child Browse files
Show More
@@ -0,0 +1,295
1 #!/usr/bin/env python3
2
3
4 import re
5 import sys
6
7
8 ignored_modules = set('''
9 argparse
10 base64
11 bcrypt
12 binascii
13 bleach
14 calendar
15 celery
16 celery
17 chardet
18 click
19 collections
20 configparser
21 copy
22 csv
23 ctypes
24 datetime
25 dateutil
26 decimal
27 decorator
28 difflib
29 distutils
30 docutils
31 email
32 errno
33 fileinput
34 functools
35 getpass
36 grp
37 hashlib
38 hmac
39 html
40 http
41 imp
42 importlib
43 inspect
44 io
45 ipaddr
46 IPython
47 isapi_wsgi
48 itertools
49 json
50 kajiki
51 ldap
52 logging
53 mako
54 markdown
55 mimetypes
56 mock
57 msvcrt
58 multiprocessing
59 operator
60 os
61 paginate
62 paginate_sqlalchemy
63 pam
64 paste
65 pkg_resources
66 platform
67 posixpath
68 pprint
69 pwd
70 pyflakes
71 pytest
72 pytest_localserver
73 random
74 re
75 routes
76 setuptools
77 shlex
78 shutil
79 smtplib
80 socket
81 ssl
82 stat
83 string
84 struct
85 subprocess
86 sys
87 tarfile
88 tempfile
89 textwrap
90 tgext
91 threading
92 time
93 traceback
94 traitlets
95 types
96 urllib
97 urlobject
98 uuid
99 warnings
100 webhelpers2
101 webob
102 webtest
103 whoosh
104 win32traceutil
105 zipfile
106 '''.split())
107
108 top_modules = set('''
109 kallithea.alembic
110 kallithea.bin
111 kallithea.config
112 kallithea.controllers
113 kallithea.templates.py
114 scripts
115 '''.split())
116
117 bottom_external_modules = set('''
118 tg
119 mercurial
120 sqlalchemy
121 alembic
122 formencode
123 pygments
124 dulwich
125 beaker
126 psycopg2
127 docs
128 setup
129 conftest
130 '''.split())
131
132 normal_modules = set('''
133 kallithea
134 kallithea.lib.celerylib.tasks
135 kallithea.lib
136 kallithea.lib.auth
137 kallithea.lib.auth_modules
138 kallithea.lib.base
139 kallithea.lib.celerylib
140 kallithea.lib.db_manage
141 kallithea.lib.helpers
142 kallithea.lib.hooks
143 kallithea.lib.indexers
144 kallithea.lib.utils
145 kallithea.lib.utils2
146 kallithea.lib.vcs
147 kallithea.lib.webutils
148 kallithea.model
149 kallithea.model.scm
150 kallithea.templates.py
151 '''.split())
152
153 shown_modules = normal_modules | top_modules
154
155 # break the chains somehow - this is a cleanup TODO list
156 known_violations = [
157 ('kallithea.lib.auth_modules', 'kallithea.lib.auth'), # needs base&facade
158 ('kallithea.lib.utils', 'kallithea.model'), # clean up utils
159 ('kallithea.lib.utils', 'kallithea.model.db'),
160 ('kallithea.lib.utils', 'kallithea.model.scm'),
161 ('kallithea.lib.celerylib.tasks', 'kallithea.lib.helpers'),
162 ('kallithea.lib.celerylib.tasks', 'kallithea.lib.hooks'),
163 ('kallithea.lib.celerylib.tasks', 'kallithea.lib.indexers'),
164 ('kallithea.lib.celerylib.tasks', 'kallithea.model'),
165 ('kallithea.model', 'kallithea.lib.auth'), # auth.HasXXX
166 ('kallithea.model', 'kallithea.lib.auth_modules'), # validators
167 ('kallithea.model', 'kallithea.lib.helpers'),
168 ('kallithea.model', 'kallithea.lib.hooks'), # clean up hooks
169 ('kallithea.model', 'kallithea.model.scm'),
170 ('kallithea.model.scm', 'kallithea.lib.hooks'),
171 ]
172
173 extra_edges = [
174 ('kallithea.config', 'kallithea.controllers'), # through TG
175 ('kallithea.lib.auth', 'kallithea.lib.auth_modules'), # custom loader
176 ]
177
178
179 def normalize(s):
180 """Given a string with dot path, return the string it should be shown as."""
181 parts = s.replace('.__init__', '').split('.')
182 short_2 = '.'.join(parts[:2])
183 short_3 = '.'.join(parts[:3])
184 short_4 = '.'.join(parts[:4])
185 if parts[0] in ['scripts', 'contributor_data', 'i18n_utils']:
186 return 'scripts'
187 if short_3 == 'kallithea.model.meta':
188 return 'kallithea.model.db'
189 if parts[:4] == ['kallithea', 'lib', 'vcs', 'ssh']:
190 return 'kallithea.lib.vcs.ssh'
191 if short_4 in shown_modules:
192 return short_4
193 if short_3 in shown_modules:
194 return short_3
195 if short_2 in shown_modules:
196 return short_2
197 if short_2 == 'kallithea.tests':
198 return None
199 if parts[0] in ignored_modules:
200 return None
201 assert parts[0] in bottom_external_modules, parts
202 return parts[0]
203
204
205 def main(filenames):
206 if not filenames or filenames[0].startswith('-'):
207 print('''\
208 Usage:
209 hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/deps.py
210 dot -Tsvg deps.dot > deps.svg
211 ''')
212 raise SystemExit(1)
213
214 files_imports = dict() # map filenames to its imports
215 import_deps = set() # set of tuples with module name and its imports
216 for fn in filenames:
217 with open(fn) as f:
218 s = f.read()
219
220 dot_name = (fn[:-3] if fn.endswith('.py') else fn).replace('/', '.')
221 file_imports = set()
222 for m in re.finditer(r'^ *(?:from ([^ ]*) import (?:([a-zA-Z].*)|\(([^)]*)\))|import (.*))$', s, re.MULTILINE):
223 m_from, m_from_import, m_from_import2, m_import = m.groups()
224 if m_from:
225 pre = m_from + '.'
226 if pre.startswith('.'):
227 pre = dot_name.rsplit('.', 1)[0] + pre
228 importlist = m_from_import or m_from_import2
229 else:
230 pre = ''
231 importlist = m_import
232 for imp in importlist.split('#', 1)[0].split(','):
233 full_imp = pre + imp.strip().split(' as ', 1)[0]
234 file_imports.add(full_imp)
235 import_deps.add((dot_name, full_imp))
236 files_imports[fn] = file_imports
237
238 # dump out all deps for debugging and analysis
239 with open('deps.txt', 'w') as f:
240 for fn, file_imports in sorted(files_imports.items()):
241 for file_import in sorted(file_imports):
242 if file_import.split('.', 1)[0] in ignored_modules:
243 continue
244 f.write('%s: %s\n' % (fn, file_import))
245
246 # find leafs that haven't been ignored - they are the important external dependencies and shown in the bottom row
247 only_imported = set(
248 set(normalize(b) for a, b in import_deps) -
249 set(normalize(a) for a, b in import_deps) -
250 set([None, 'kallithea'])
251 )
252
253 normalized_dep_edges = set()
254 for dot_name, full_imp in import_deps:
255 a = normalize(dot_name)
256 b = normalize(full_imp)
257 if a is None or b is None or a == b:
258 continue
259 normalized_dep_edges.add((a, b))
260 #print((dot_name, full_imp, a, b))
261 normalized_dep_edges.update(extra_edges)
262
263 unseen_shown_modules = shown_modules.difference(a for a, b in normalized_dep_edges).difference(b for a, b in normalized_dep_edges)
264 assert not unseen_shown_modules, unseen_shown_modules
265
266 with open('deps.dot', 'w') as f:
267 f.write('digraph {\n')
268 f.write('subgraph { rank = same; %s}\n' % ''.join('"%s"; ' % s for s in sorted(top_modules)))
269 f.write('subgraph { rank = same; %s}\n' % ''.join('"%s"; ' % s for s in sorted(only_imported)))
270 for a, b in sorted(normalized_dep_edges):
271 f.write(' "%s" -> "%s"%s\n' % (a, b, ' [color=red]' if (a, b) in known_violations else ' [color=green]' if (a, b) in extra_edges else ''))
272 f.write('}\n')
273
274 # verify dependencies by untangling dependency chain bottom-up:
275 todo = set(normalized_dep_edges)
276 for x in known_violations:
277 todo.remove(x)
278
279 while todo:
280 depending = set(a for a, b in todo)
281 depended = set(b for a, b in todo)
282 drop = depended - depending
283 if not drop:
284 print('ERROR: cycles:', len(todo))
285 for x in sorted(todo):
286 print('%s,' % (x,))
287 raise SystemExit(1)
288 #for do_b in sorted(drop):
289 # print('Picking', do_b, '- unblocks:', ' '.join(a for a, b in sorted((todo)) if b == do_b))
290 todo = set((a, b) for a, b in todo if b in depending)
291 #print()
292
293
294 if __name__ == '__main__':
295 main(sys.argv[1:])
@@ -1,55 +1,58
1 syntax: glob
1 syntax: glob
2 *.pyc
2 *.pyc
3 *.swp
3 *.swp
4 *.sqlite
4 *.sqlite
5 *.tox
5 *.tox
6 *.egg-info
6 *.egg-info
7 *.egg
7 *.egg
8 *.mo
8 *.mo
9 *.orig
9 *.orig
10 *.rej
10 *.rej
11 *.bak
11 *.bak
12 .eggs/
12 .eggs/
13
13
14 syntax: regexp
14 syntax: regexp
15 ^extensions\.py$
15 ^extensions\.py$
16 ^build$
16 ^build$
17 ^dist$
17 ^dist$
18 ^docs/build$
18 ^docs/build$
19 ^docs/_build$
19 ^docs/_build$
20 ^data$
20 ^data$
21 ^sql_dumps$
21 ^sql_dumps$
22 ^\.settings$
22 ^\.settings$
23 ^\.project$
23 ^\.project$
24 ^\.pydevproject$
24 ^\.pydevproject$
25 ^\.coverage$
25 ^\.coverage$
26 ^kallithea/front-end/node_modules$
26 ^kallithea/front-end/node_modules$
27 ^kallithea/front-end/package-lock\.json$
27 ^kallithea/front-end/package-lock\.json$
28 ^kallithea/front-end/theme\.less$
28 ^kallithea/front-end/theme\.less$
29 ^kallithea/front-end/tmp$
29 ^kallithea/front-end/tmp$
30 ^kallithea/public/codemirror$
30 ^kallithea/public/codemirror$
31 ^kallithea/public/css/select2-spinner\.gif$
31 ^kallithea/public/css/select2-spinner\.gif$
32 ^kallithea/public/css/select2\.png$
32 ^kallithea/public/css/select2\.png$
33 ^kallithea/public/css/select2x2\.png$
33 ^kallithea/public/css/select2x2\.png$
34 ^kallithea/public/css/style\.css$
34 ^kallithea/public/css/style\.css$
35 ^kallithea/public/css/style\.css\.map$
35 ^kallithea/public/css/style\.css\.map$
36 ^kallithea/public/js/bootstrap\.js$
36 ^kallithea/public/js/bootstrap\.js$
37 ^kallithea/public/js/dataTables\.bootstrap\.js$
37 ^kallithea/public/js/dataTables\.bootstrap\.js$
38 ^kallithea/public/js/jquery\.atwho\.min\.js$
38 ^kallithea/public/js/jquery\.atwho\.min\.js$
39 ^kallithea/public/js/jquery\.caret\.min\.js$
39 ^kallithea/public/js/jquery\.caret\.min\.js$
40 ^kallithea/public/js/jquery\.dataTables\.js$
40 ^kallithea/public/js/jquery\.dataTables\.js$
41 ^kallithea/public/js/jquery\.flot\.js$
41 ^kallithea/public/js/jquery\.flot\.js$
42 ^kallithea/public/js/jquery\.flot\.selection\.js$
42 ^kallithea/public/js/jquery\.flot\.selection\.js$
43 ^kallithea/public/js/jquery\.flot\.time\.js$
43 ^kallithea/public/js/jquery\.flot\.time\.js$
44 ^kallithea/public/js/jquery\.min\.js$
44 ^kallithea/public/js/jquery\.min\.js$
45 ^kallithea/public/js/select2\.js$
45 ^kallithea/public/js/select2\.js$
46 ^kallithea\.db$
46 ^kallithea\.db$
47 ^test\.db$
47 ^test\.db$
48 ^Kallithea\.egg-info$
48 ^Kallithea\.egg-info$
49 ^my\.ini$
49 ^my\.ini$
50 ^fabfile\.py$
50 ^fabfile\.py$
51 ^\.idea$
51 ^\.idea$
52 ^\.cache$
52 ^\.cache$
53 ^\.pytest_cache$
53 ^\.pytest_cache$
54 ^venv$
54 ^venv$
55 /__pycache__$
55 /__pycache__$
56 ^deps\.dot$
57 ^deps\.svg$
58 ^deps\.txt$
@@ -1,309 +1,397
1 .. _contributing:
1 .. _contributing:
2
2
3 =========================
3 =========================
4 Contributing to Kallithea
4 Contributing to Kallithea
5 =========================
5 =========================
6
6
7 Kallithea is developed and maintained by its users. Please join us and scratch
7 Kallithea is developed and maintained by its users. Please join us and scratch
8 your own itch.
8 your own itch.
9
9
10
10
11 Infrastructure
11 Infrastructure
12 --------------
12 --------------
13
13
14 The main repository is hosted on Our Own Kallithea (aka OOK) at
14 The main repository is hosted on Our Own Kallithea (aka OOK) at
15 https://kallithea-scm.org/repos/kallithea/, our self-hosted instance
15 https://kallithea-scm.org/repos/kallithea/, our self-hosted instance
16 of Kallithea.
16 of Kallithea.
17
17
18 Please use the `mailing list`_ to send patches or report issues.
18 Please use the `mailing list`_ to send patches or report issues.
19
19
20 We use Weblate_ to translate the user interface messages into languages other
20 We use Weblate_ to translate the user interface messages into languages other
21 than English. Join our project on `Hosted Weblate`_ to help us.
21 than English. Join our project on `Hosted Weblate`_ to help us.
22 To register, you can use your Bitbucket or GitHub account. See :ref:`translations`
22 To register, you can use your Bitbucket or GitHub account. See :ref:`translations`
23 for more details.
23 for more details.
24
24
25
25
26 Getting started
26 Getting started
27 ---------------
27 ---------------
28
28
29 To get started with Kallithea development run the following commands in your
29 To get started with Kallithea development run the following commands in your
30 bash shell::
30 bash shell::
31
31
32 hg clone https://kallithea-scm.org/repos/kallithea
32 hg clone https://kallithea-scm.org/repos/kallithea
33 cd kallithea
33 cd kallithea
34 python3 -m venv venv
34 python3 -m venv venv
35 . venv/bin/activate
35 . venv/bin/activate
36 pip install --upgrade pip setuptools
36 pip install --upgrade pip setuptools
37 pip install --upgrade -e . -r dev_requirements.txt python-ldap python-pam
37 pip install --upgrade -e . -r dev_requirements.txt python-ldap python-pam
38 kallithea-cli config-create my.ini
38 kallithea-cli config-create my.ini
39 kallithea-cli db-create -c my.ini --user=user --email=user@example.com --password=password --repos=/tmp
39 kallithea-cli db-create -c my.ini --user=user --email=user@example.com --password=password --repos=/tmp
40 kallithea-cli front-end-build
40 kallithea-cli front-end-build
41 gearbox serve -c my.ini --reload &
41 gearbox serve -c my.ini --reload &
42 firefox http://127.0.0.1:5000/
42 firefox http://127.0.0.1:5000/
43
43
44
44
45 Contribution flow
45 Contribution flow
46 -----------------
46 -----------------
47
47
48 Starting from an existing Kallithea clone, make sure it is up to date with the
48 Starting from an existing Kallithea clone, make sure it is up to date with the
49 latest upstream changes::
49 latest upstream changes::
50
50
51 hg pull
51 hg pull
52 hg update
52 hg update
53
53
54 Review the :ref:`contributing-guidelines` and :ref:`coding-guidelines`.
54 Review the :ref:`contributing-guidelines` and :ref:`coding-guidelines`.
55
55
56 If you are new to Mercurial, refer to Mercurial `Quick Start`_ and `Beginners
56 If you are new to Mercurial, refer to Mercurial `Quick Start`_ and `Beginners
57 Guide`_ on the Mercurial wiki.
57 Guide`_ on the Mercurial wiki.
58
58
59 Now, make some changes and test them (see :ref:`contributing-tests`). Don't
59 Now, make some changes and test them (see :ref:`contributing-tests`). Don't
60 forget to add new tests to cover new functionality or bug fixes.
60 forget to add new tests to cover new functionality or bug fixes.
61
61
62 For documentation changes, run ``make html`` from the ``docs`` directory to
62 For documentation changes, run ``make html`` from the ``docs`` directory to
63 generate the HTML result, then review them in your browser.
63 generate the HTML result, then review them in your browser.
64
64
65 Before submitting any changes, run the cleanup script::
65 Before submitting any changes, run the cleanup script::
66
66
67 ./scripts/run-all-cleanup
67 ./scripts/run-all-cleanup
68
68
69 When you are completely ready, you can send your changes to the community for
69 When you are completely ready, you can send your changes to the community for
70 review and inclusion, via the mailing list (via ``hg email``).
70 review and inclusion, via the mailing list (via ``hg email``).
71
71
72 .. _contributing-tests:
72 .. _contributing-tests:
73
73
74
74
75 Internal dependencies
76 ---------------------
77
78 We try to keep the code base clean and modular and avoid circular dependencies.
79 Code should only invoke code in layers below itself.
80
81 Imports should import whole modules ``from`` their parent module, perhaps
82 ``as`` a shortened name. Avoid imports ``from`` modules.
83
84 To avoid cycles and partially initialized modules, ``__init__.py`` should *not*
85 contain any non-trivial imports. The top level of a module should *not* be a
86 facade for the module functionality.
87
88 Common code for a module is often in ``base.py``.
89
90 The important part of the dependency graph is approximately linear. In the
91 following list, modules may only depend on modules below them:
92
93 ``tests``
94 Just get the job done - anything goes.
95
96 ``bin/`` & ``config/`` & ``alembic/``
97 The main entry points, defined in ``setup.py``. Note: The TurboGears template
98 use ``config`` for the high WSGI application - this is not for low level
99 configuration.
100
101 ``controllers/``
102 The top level web application, with TurboGears using the ``root`` controller
103 as entry point, and ``routing`` dispatching to other controllers.
104
105 ``templates/**.html``
106 The "view", rendering to HTML. Invoked by controllers which can pass them
107 anything from lower layers - especially ``helpers`` available as ``h`` will
108 cut through all layers, and ``c`` gives access to global variables.
109
110 ``lib/helpers.py``
111 High level helpers, exposing everything to templates as ``h``. It depends on
112 everything and has a huge dependency chain, so it should not be used for
113 anything else. TODO.
114
115 ``controlles/base.py``
116 The base class of controllers, with lots of model knowledge.
117
118 ``lib/auth.py``
119 All things related to authentication. TODO.
120
121 ``lib/utils.py``
122 High level utils with lots of model knowledge. TODO.
123
124 ``lib/hooks.py``
125 Hooks into "everything" to give centralized logging to database, cache
126 invalidation, and extension handling. TODO.
127
128 ``model/``
129 Convenience business logic wrappers around database models.
130
131 ``model/db.py``
132 Defines the database schema and provides some additional logic.
133
134 ``model/scm.py``
135 All things related to anything. TODO.
136
137 SQLAlchemy
138 Database session and transaction in thread-local variables.
139
140 ``lib/utils2.py``
141 Low level utils specific to Kallithea.
142
143 ``lib/webutils.py``
144 Low level generic utils with awareness of the TurboGears environment.
145
146 TurboGears
147 Request, response and state like i18n gettext in thread-local variables.
148 External dependency with global state - usage should be minimized.
149
150 ``lib/vcs/``
151 Previously an independent library. No awareness of web, database, or state.
152
153 ``lib/*``
154 Various "pure" functionality not depending on anything else.
155
156 ``__init__``
157 Very basic Kallithea constants - some of them are set very early based on ``.ini``.
158
159 This is not exactly how it is right now, but we aim for something like that.
160 Especially the areas marked as TODO have some problems that need untangling.
161
162
75 Running tests
163 Running tests
76 -------------
164 -------------
77
165
78 After finishing your changes make sure all tests pass cleanly. Run the testsuite
166 After finishing your changes make sure all tests pass cleanly. Run the testsuite
79 by invoking ``py.test`` from the project root::
167 by invoking ``py.test`` from the project root::
80
168
81 py.test
169 py.test
82
170
83 Note that on unix systems, the temporary directory (``/tmp`` or where
171 Note that on unix systems, the temporary directory (``/tmp`` or where
84 ``$TMPDIR`` points) must allow executable files; Git hooks must be executable,
172 ``$TMPDIR`` points) must allow executable files; Git hooks must be executable,
85 and the test suite creates repositories in the temporary directory. Linux
173 and the test suite creates repositories in the temporary directory. Linux
86 systems with /tmp mounted noexec will thus fail.
174 systems with /tmp mounted noexec will thus fail.
87
175
88 Tests can be run on PostgreSQL like::
176 Tests can be run on PostgreSQL like::
89
177
90 sudo -u postgres createuser 'kallithea-test' --pwprompt # password password
178 sudo -u postgres createuser 'kallithea-test' --pwprompt # password password
91 sudo -u postgres createdb 'kallithea-test' --owner 'kallithea-test'
179 sudo -u postgres createdb 'kallithea-test' --owner 'kallithea-test'
92 REUSE_TEST_DB='postgresql://kallithea-test:password@localhost/kallithea-test' py.test
180 REUSE_TEST_DB='postgresql://kallithea-test:password@localhost/kallithea-test' py.test
93
181
94 Tests can be run on MariaDB/MySQL like::
182 Tests can be run on MariaDB/MySQL like::
95
183
96 echo "GRANT ALL PRIVILEGES ON \`kallithea-test\`.* TO 'kallithea-test'@'localhost' IDENTIFIED BY 'password'" | sudo -u mysql mysql
184 echo "GRANT ALL PRIVILEGES ON \`kallithea-test\`.* TO 'kallithea-test'@'localhost' IDENTIFIED BY 'password'" | sudo -u mysql mysql
97 TEST_DB='mysql://kallithea-test:password@localhost/kallithea-test?charset=utf8mb4' py.test
185 TEST_DB='mysql://kallithea-test:password@localhost/kallithea-test?charset=utf8mb4' py.test
98
186
99 You can also use ``tox`` to run the tests with all supported Python versions.
187 You can also use ``tox`` to run the tests with all supported Python versions.
100
188
101 When running tests, Kallithea generates a `test.ini` based on template values
189 When running tests, Kallithea generates a `test.ini` based on template values
102 in `kallithea/tests/conftest.py` and populates the SQLite database specified
190 in `kallithea/tests/conftest.py` and populates the SQLite database specified
103 there.
191 there.
104
192
105 It is possible to avoid recreating the full test database on each invocation of
193 It is possible to avoid recreating the full test database on each invocation of
106 the tests, thus eliminating the initial delay. To achieve this, run the tests as::
194 the tests, thus eliminating the initial delay. To achieve this, run the tests as::
107
195
108 gearbox serve -c /tmp/kallithea-test-XXX/test.ini --pid-file=test.pid --daemon
196 gearbox serve -c /tmp/kallithea-test-XXX/test.ini --pid-file=test.pid --daemon
109 KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 py.test
197 KALLITHEA_WHOOSH_TEST_DISABLE=1 KALLITHEA_NO_TMP_PATH=1 py.test
110 kill -9 $(cat test.pid)
198 kill -9 $(cat test.pid)
111
199
112 In these commands, the following variables are used::
200 In these commands, the following variables are used::
113
201
114 KALLITHEA_WHOOSH_TEST_DISABLE=1 - skip whoosh index building and tests
202 KALLITHEA_WHOOSH_TEST_DISABLE=1 - skip whoosh index building and tests
115 KALLITHEA_NO_TMP_PATH=1 - disable new temp path for tests, used mostly for testing_vcs_operations
203 KALLITHEA_NO_TMP_PATH=1 - disable new temp path for tests, used mostly for testing_vcs_operations
116
204
117 You can run individual tests by specifying their path as argument to py.test.
205 You can run individual tests by specifying their path as argument to py.test.
118 py.test also has many more options, see `py.test -h`. Some useful options
206 py.test also has many more options, see `py.test -h`. Some useful options
119 are::
207 are::
120
208
121 -k EXPRESSION only run tests which match the given substring
209 -k EXPRESSION only run tests which match the given substring
122 expression. An expression is a python evaluable
210 expression. An expression is a python evaluable
123 expression where all names are substring-matched
211 expression where all names are substring-matched
124 against test names and their parent classes. Example:
212 against test names and their parent classes. Example:
125 -x, --exitfirst exit instantly on first error or failed test.
213 -x, --exitfirst exit instantly on first error or failed test.
126 --lf rerun only the tests that failed at the last run (or
214 --lf rerun only the tests that failed at the last run (or
127 all if none failed)
215 all if none failed)
128 --ff run all tests but run the last failures first. This
216 --ff run all tests but run the last failures first. This
129 may re-order tests and thus lead to repeated fixture
217 may re-order tests and thus lead to repeated fixture
130 setup/teardown
218 setup/teardown
131 --pdb start the interactive Python debugger on errors.
219 --pdb start the interactive Python debugger on errors.
132 -s, --capture=no don't capture stdout (any stdout output will be
220 -s, --capture=no don't capture stdout (any stdout output will be
133 printed immediately)
221 printed immediately)
134
222
135 Performance tests
223 Performance tests
136 ^^^^^^^^^^^^^^^^^
224 ^^^^^^^^^^^^^^^^^
137
225
138 A number of performance tests are present in the test suite, but they are
226 A number of performance tests are present in the test suite, but they are
139 not run in a standard test run. These tests are useful to
227 not run in a standard test run. These tests are useful to
140 evaluate the impact of certain code changes with respect to performance.
228 evaluate the impact of certain code changes with respect to performance.
141
229
142 To run these tests::
230 To run these tests::
143
231
144 env TEST_PERFORMANCE=1 py.test kallithea/tests/performance
232 env TEST_PERFORMANCE=1 py.test kallithea/tests/performance
145
233
146 To analyze performance, you could install pytest-profiling_, which enables the
234 To analyze performance, you could install pytest-profiling_, which enables the
147 --profile and --profile-svg options to py.test.
235 --profile and --profile-svg options to py.test.
148
236
149 .. _pytest-profiling: https://github.com/manahl/pytest-plugins/tree/master/pytest-profiling
237 .. _pytest-profiling: https://github.com/manahl/pytest-plugins/tree/master/pytest-profiling
150
238
151 .. _contributing-guidelines:
239 .. _contributing-guidelines:
152
240
153
241
154 Contribution guidelines
242 Contribution guidelines
155 -----------------------
243 -----------------------
156
244
157 Kallithea is GPLv3 and we assume all contributions are made by the
245 Kallithea is GPLv3 and we assume all contributions are made by the
158 committer/contributor and under GPLv3 unless explicitly stated. We do care a
246 committer/contributor and under GPLv3 unless explicitly stated. We do care a
159 lot about preservation of copyright and license information for existing code
247 lot about preservation of copyright and license information for existing code
160 that is brought into the project.
248 that is brought into the project.
161
249
162 Contributions will be accepted in most formats -- such as commits hosted on your
250 Contributions will be accepted in most formats -- such as commits hosted on your
163 own Kallithea instance, or patches sent by email to the `kallithea-general`_
251 own Kallithea instance, or patches sent by email to the `kallithea-general`_
164 mailing list.
252 mailing list.
165
253
166 Make sure to test your changes both manually and with the automatic tests
254 Make sure to test your changes both manually and with the automatic tests
167 before posting.
255 before posting.
168
256
169 We care about quality and review and keeping a clean repository history. We
257 We care about quality and review and keeping a clean repository history. We
170 might give feedback that requests polishing contributions until they are
258 might give feedback that requests polishing contributions until they are
171 "perfect". We might also rebase and collapse and make minor adjustments to your
259 "perfect". We might also rebase and collapse and make minor adjustments to your
172 changes when we apply them.
260 changes when we apply them.
173
261
174 We try to make sure we have consensus on the direction the project is taking.
262 We try to make sure we have consensus on the direction the project is taking.
175 Everything non-sensitive should be discussed in public -- preferably on the
263 Everything non-sensitive should be discussed in public -- preferably on the
176 mailing list. We aim at having all non-trivial changes reviewed by at least
264 mailing list. We aim at having all non-trivial changes reviewed by at least
177 one other core developer before pushing. Obvious non-controversial changes will
265 one other core developer before pushing. Obvious non-controversial changes will
178 be handled more casually.
266 be handled more casually.
179
267
180 There is a main development branch ("default") which is generally stable so that
268 There is a main development branch ("default") which is generally stable so that
181 it can be (and is) used in production. There is also a "stable" branch that is
269 it can be (and is) used in production. There is also a "stable" branch that is
182 almost exclusively reserved for bug fixes or trivial changes. Experimental
270 almost exclusively reserved for bug fixes or trivial changes. Experimental
183 changes should live elsewhere (for example in a pull request) until they are
271 changes should live elsewhere (for example in a pull request) until they are
184 ready.
272 ready.
185
273
186 .. _coding-guidelines:
274 .. _coding-guidelines:
187
275
188
276
189 Coding guidelines
277 Coding guidelines
190 -----------------
278 -----------------
191
279
192 We don't have a formal coding/formatting standard. We are currently using a mix
280 We don't have a formal coding/formatting standard. We are currently using a mix
193 of Mercurial's (https://www.mercurial-scm.org/wiki/CodingStyle), pep8, and
281 of Mercurial's (https://www.mercurial-scm.org/wiki/CodingStyle), pep8, and
194 consistency with existing code. Run ``scripts/run-all-cleanup`` before
282 consistency with existing code. Run ``scripts/run-all-cleanup`` before
195 committing to ensure some basic code formatting consistency.
283 committing to ensure some basic code formatting consistency.
196
284
197 We support Python 3.6 and later.
285 We support Python 3.6 and later.
198
286
199 We try to support the most common modern web browsers. IE9 is still supported
287 We try to support the most common modern web browsers. IE9 is still supported
200 to the extent it is feasible, IE8 is not.
288 to the extent it is feasible, IE8 is not.
201
289
202 We primarily support Linux and OS X on the server side but Windows should also work.
290 We primarily support Linux and OS X on the server side but Windows should also work.
203
291
204 HTML templates should use 2 spaces for indentation ... but be pragmatic. We
292 HTML templates should use 2 spaces for indentation ... but be pragmatic. We
205 should use templates cleverly and avoid duplication. We should use reasonable
293 should use templates cleverly and avoid duplication. We should use reasonable
206 semantic markup with element classes and IDs that can be used for styling and testing.
294 semantic markup with element classes and IDs that can be used for styling and testing.
207 We should only use inline styles in places where it really is semantic (such as
295 We should only use inline styles in places where it really is semantic (such as
208 ``display: none``).
296 ``display: none``).
209
297
210 JavaScript must use ``;`` between/after statements. Indentation 4 spaces. Inline
298 JavaScript must use ``;`` between/after statements. Indentation 4 spaces. Inline
211 multiline functions should be indented two levels -- one for the ``()`` and one for
299 multiline functions should be indented two levels -- one for the ``()`` and one for
212 ``{}``.
300 ``{}``.
213 Variables holding jQuery objects should be named with a leading ``$``.
301 Variables holding jQuery objects should be named with a leading ``$``.
214
302
215 Commit messages should have a leading short line summarizing the changes. For
303 Commit messages should have a leading short line summarizing the changes. For
216 bug fixes, put ``(Issue #123)`` at the end of this line.
304 bug fixes, put ``(Issue #123)`` at the end of this line.
217
305
218 Use American English grammar and spelling overall. Use `English title case`_ for
306 Use American English grammar and spelling overall. Use `English title case`_ for
219 page titles, button labels, headers, and 'labels' for fields in forms.
307 page titles, button labels, headers, and 'labels' for fields in forms.
220
308
221 .. _English title case: https://en.wikipedia.org/wiki/Capitalization#Title_case
309 .. _English title case: https://en.wikipedia.org/wiki/Capitalization#Title_case
222
310
223 Template helpers (that is, everything in ``kallithea.lib.helpers``)
311 Template helpers (that is, everything in ``kallithea.lib.helpers``)
224 should only be referenced from templates. If you need to call a
312 should only be referenced from templates. If you need to call a
225 helper from the Python code, consider moving the function somewhere
313 helper from the Python code, consider moving the function somewhere
226 else (e.g. to the model).
314 else (e.g. to the model).
227
315
228 Notes on the SQLAlchemy session
316 Notes on the SQLAlchemy session
229 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
317 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
230
318
231 Each HTTP request runs inside an independent SQLAlchemy session (as well
319 Each HTTP request runs inside an independent SQLAlchemy session (as well
232 as in an independent database transaction). ``Session`` is the session manager
320 as in an independent database transaction). ``Session`` is the session manager
233 and factory. ``Session()`` will create a new session on-demand or return the
321 and factory. ``Session()`` will create a new session on-demand or return the
234 current session for the active thread. Many database operations are methods on
322 current session for the active thread. Many database operations are methods on
235 such session instances. The session will generally be removed by
323 such session instances. The session will generally be removed by
236 TurboGears automatically.
324 TurboGears automatically.
237
325
238 Database model objects
326 Database model objects
239 (almost) always belong to a particular SQLAlchemy session, which means
327 (almost) always belong to a particular SQLAlchemy session, which means
240 that SQLAlchemy will ensure that they're kept in sync with the database
328 that SQLAlchemy will ensure that they're kept in sync with the database
241 (but also means that they cannot be shared across requests).
329 (but also means that they cannot be shared across requests).
242
330
243 Objects can be added to the session using ``Session().add``, but this is
331 Objects can be added to the session using ``Session().add``, but this is
244 rarely needed:
332 rarely needed:
245
333
246 * When creating a database object by calling the constructor directly,
334 * When creating a database object by calling the constructor directly,
247 it must explicitly be added to the session.
335 it must explicitly be added to the session.
248
336
249 * When creating an object using a factory function (like
337 * When creating an object using a factory function (like
250 ``create_repo``), the returned object has already (by convention)
338 ``create_repo``), the returned object has already (by convention)
251 been added to the session, and should not be added again.
339 been added to the session, and should not be added again.
252
340
253 * When getting an object from the session (via ``Session().query`` or
341 * When getting an object from the session (via ``Session().query`` or
254 any of the utility functions that look up objects in the database),
342 any of the utility functions that look up objects in the database),
255 it's already part of the session, and should not be added again.
343 it's already part of the session, and should not be added again.
256 SQLAlchemy monitors attribute modifications automatically for all
344 SQLAlchemy monitors attribute modifications automatically for all
257 objects it knows about and syncs them to the database.
345 objects it knows about and syncs them to the database.
258
346
259 SQLAlchemy also flushes changes to the database automatically; manually
347 SQLAlchemy also flushes changes to the database automatically; manually
260 calling ``Session().flush`` is usually only necessary when the Python
348 calling ``Session().flush`` is usually only necessary when the Python
261 code needs the database to assign an "auto-increment" primary key ID to
349 code needs the database to assign an "auto-increment" primary key ID to
262 a freshly created model object (before flushing, the ID attribute will
350 a freshly created model object (before flushing, the ID attribute will
263 be ``None``).
351 be ``None``).
264
352
265 Debugging
353 Debugging
266 ^^^^^^^^^
354 ^^^^^^^^^
267
355
268 A good way to trace what Kallithea is doing is to keep an eye on the output on
356 A good way to trace what Kallithea is doing is to keep an eye on the output on
269 stdout/stderr of the server process. Perhaps change ``my.ini`` to log at
357 stdout/stderr of the server process. Perhaps change ``my.ini`` to log at
270 ``DEBUG`` or ``INFO`` level, especially ``[logger_kallithea]``, but perhaps
358 ``DEBUG`` or ``INFO`` level, especially ``[logger_kallithea]``, but perhaps
271 also other loggers. It is often easier to add additional ``log`` or ``print``
359 also other loggers. It is often easier to add additional ``log`` or ``print``
272 statements than to use a Python debugger.
360 statements than to use a Python debugger.
273
361
274 Sometimes it is simpler to disable ``errorpage.enabled`` and perhaps also
362 Sometimes it is simpler to disable ``errorpage.enabled`` and perhaps also
275 ``trace_errors.enable`` to expose raw errors instead of adding extra
363 ``trace_errors.enable`` to expose raw errors instead of adding extra
276 processing. Enabling ``debug`` can be helpful for showing and exploring
364 processing. Enabling ``debug`` can be helpful for showing and exploring
277 tracebacks in the browser, but is also insecure and will add extra processing.
365 tracebacks in the browser, but is also insecure and will add extra processing.
278
366
279 TurboGears2 DebugBar
367 TurboGears2 DebugBar
280 ^^^^^^^^^^^^^^^^^^^^
368 ^^^^^^^^^^^^^^^^^^^^
281
369
282 It is possible to enable the TurboGears2-provided DebugBar_, a toolbar overlayed
370 It is possible to enable the TurboGears2-provided DebugBar_, a toolbar overlayed
283 over the Kallithea web interface, allowing you to see:
371 over the Kallithea web interface, allowing you to see:
284
372
285 * timing information of the current request, including profiling information
373 * timing information of the current request, including profiling information
286 * request data, including GET data, POST data, cookies, headers and environment
374 * request data, including GET data, POST data, cookies, headers and environment
287 variables
375 variables
288 * a list of executed database queries, including timing and result values
376 * a list of executed database queries, including timing and result values
289
377
290 DebugBar is only activated when ``debug = true`` is set in the configuration
378 DebugBar is only activated when ``debug = true`` is set in the configuration
291 file. This is important, because the DebugBar toolbar will be visible for all
379 file. This is important, because the DebugBar toolbar will be visible for all
292 users, and allow them to see information they should not be allowed to see. Like
380 users, and allow them to see information they should not be allowed to see. Like
293 is anyway the case for ``debug = true``, do not use this in production!
381 is anyway the case for ``debug = true``, do not use this in production!
294
382
295 To enable DebugBar, install ``tgext.debugbar`` and ``kajiki`` (typically via
383 To enable DebugBar, install ``tgext.debugbar`` and ``kajiki`` (typically via
296 ``pip``) and restart Kallithea (in debug mode).
384 ``pip``) and restart Kallithea (in debug mode).
297
385
298
386
299 Thank you for your contribution!
387 Thank you for your contribution!
300 --------------------------------
388 --------------------------------
301
389
302
390
303 .. _Weblate: http://weblate.org/
391 .. _Weblate: http://weblate.org/
304 .. _mailing list: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general
392 .. _mailing list: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general
305 .. _kallithea-general: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general
393 .. _kallithea-general: http://lists.sfconservancy.org/mailman/listinfo/kallithea-general
306 .. _Hosted Weblate: https://hosted.weblate.org/projects/kallithea/kallithea/
394 .. _Hosted Weblate: https://hosted.weblate.org/projects/kallithea/kallithea/
307 .. _DebugBar: https://github.com/TurboGears/tgext.debugbar
395 .. _DebugBar: https://github.com/TurboGears/tgext.debugbar
308 .. _Quick Start: https://www.mercurial-scm.org/wiki/QuickStart
396 .. _Quick Start: https://www.mercurial-scm.org/wiki/QuickStart
309 .. _Beginners Guide: https://www.mercurial-scm.org/wiki/BeginnersGuides
397 .. _Beginners Guide: https://www.mercurial-scm.org/wiki/BeginnersGuides
@@ -1,14 +1,17
1 #!/bin/sh
1 #!/bin/sh
2
2
3 # Convenience script for running various idempotent source code cleanup scripts
3 # Convenience script for running various idempotent source code cleanup scripts
4
4
5 set -e
5 set -e
6 set -x
6 set -x
7
7
8 hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/deps.py
9 dot -Tsvg deps.dot > deps.svg
10
8 scripts/docs-headings.py
11 scripts/docs-headings.py
9 scripts/generate-ini.py
12 scripts/generate-ini.py
10 scripts/whitespacecleanup.sh
13 scripts/whitespacecleanup.sh
11 hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/source_format.py
14 hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/source_format.py
12
15
13 hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/pyflakes
16 hg files 'set:!binary()&grep("^#!.*python")' 'set:**.py' | xargs scripts/pyflakes
14 echo "no blocking problems found by $0"
17 echo "no blocking problems found by $0"
General Comments 0
You need to be logged in to leave comments. Login now