grimoirelab-core 0.0.1rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. grimoirelab/core/__init__.py +0 -0
  2. grimoirelab/core/_version.py +2 -0
  3. grimoirelab/core/app/__init__.py +0 -0
  4. grimoirelab/core/app/asgi.py +9 -0
  5. grimoirelab/core/app/urls.py +12 -0
  6. grimoirelab/core/app/wsgi.py +10 -0
  7. grimoirelab/core/config/__init__.py +0 -0
  8. grimoirelab/core/config/settings.py +307 -0
  9. grimoirelab/core/models.py +77 -0
  10. grimoirelab/core/runner/__init__.py +0 -0
  11. grimoirelab/core/runner/cmd.py +72 -0
  12. grimoirelab/core/runner/commands/__init__.py +0 -0
  13. grimoirelab/core/runner/commands/admin.py +163 -0
  14. grimoirelab/core/runner/commands/fetch.py +110 -0
  15. grimoirelab/core/runner/commands/run.py +114 -0
  16. grimoirelab/core/scheduler/__init__.py +0 -0
  17. grimoirelab/core/scheduler/api.py +178 -0
  18. grimoirelab/core/scheduler/apps.py +30 -0
  19. grimoirelab/core/scheduler/db.py +86 -0
  20. grimoirelab/core/scheduler/errors.py +83 -0
  21. grimoirelab/core/scheduler/jobs.py +154 -0
  22. grimoirelab/core/scheduler/models.py +359 -0
  23. grimoirelab/core/scheduler/scheduler.py +306 -0
  24. grimoirelab/core/scheduler/tasks/__init__.py +0 -0
  25. grimoirelab/core/scheduler/tasks/apps.py +26 -0
  26. grimoirelab/core/scheduler/tasks/chronicler.py +358 -0
  27. grimoirelab/core/scheduler/tasks/migrations/0001_initial.py +63 -0
  28. grimoirelab/core/scheduler/tasks/migrations/__init__.py +0 -0
  29. grimoirelab/core/scheduler/tasks/models.py +164 -0
  30. grimoirelab/core/scheduler/urls.py +32 -0
  31. grimoirelab/core/scheduler/views.py +80 -0
  32. grimoirelab/core/scheduler/worker.py +54 -0
  33. grimoirelab_core-0.0.1rc2.dist-info/LICENSE +674 -0
  34. grimoirelab_core-0.0.1rc2.dist-info/METADATA +160 -0
  35. grimoirelab_core-0.0.1rc2.dist-info/RECORD +37 -0
  36. grimoirelab_core-0.0.1rc2.dist-info/WHEEL +4 -0
  37. grimoirelab_core-0.0.1rc2.dist-info/entry_points.txt +3 -0
File without changes
@@ -0,0 +1,2 @@
1
+ # File auto-generated by semverup on 2025-01-16 11:50:05.274707
2
+ __version__ = "0.0.1-rc.2"
File without changes
@@ -0,0 +1,9 @@
1
+ """ASGI config for grimoirelab project"""
2
+
3
+ import os
4
+
5
+ from django.core.asgi import get_asgi_application
6
+
7
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'grimoirelab.core.config.settings')
8
+
9
+ application = get_asgi_application()
@@ -0,0 +1,12 @@
1
+ """GrimoireLab URL Configuration"""
2
+
3
+
4
+ from django.urls import path, include, re_path
5
+ from django.views.generic import TemplateView
6
+
7
+ from grimoirelab.core.scheduler.urls import urlpatterns as sched_urlpatterns
8
+
9
+ urlpatterns = [
10
+ path("scheduler/", include(sched_urlpatterns)),
11
+ re_path(r'^(?!static|scheduler).*$', TemplateView.as_view(template_name="index.html"))
12
+ ]
@@ -0,0 +1,10 @@
1
+ """WSGI config for GrimoireLab."""
2
+
3
+ import os
4
+
5
+ from django.core.wsgi import get_wsgi_application
6
+
7
+
8
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
9
+
10
+ application = get_wsgi_application()
File without changes
@@ -0,0 +1,307 @@
1
+ #
2
+ # GrimoireLab core basic settings file.
3
+ #
4
+ # This file defines the required settings to run GrimoireLab.
5
+ # Due to GrimoireLab is a Django based app, these settings are
6
+ # based on the configuration file generated by Django by
7
+ # default.
8
+ #
9
+ # Please check the next links for details about the configuration
10
+ # in a production environment:
11
+ #
12
+ # https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
13
+ # https://docs.djangoproject.com/en/4.2/ref/settings/
14
+ #
15
+
16
+ import os
17
+ from pathlib import Path
18
+
19
+ BASE_DIR = Path(__file__).resolve().parent.parent
20
+
21
+ SILENCED_SYSTEM_CHECKS = [
22
+ 'django_mysql.E016'
23
+ ]
24
+
25
+ #
26
+ # General app parameters
27
+ #
28
+
29
+ #
30
+ # You must never enable debug in production.
31
+ #
32
+ # https://docs.djangoproject.com/en/4.2/ref/settings/#std:setting-DEBUG
33
+ #
34
+
35
+ DEBUG = os.environ.get('GRIMOIRELAB_DEBUG', 'False').lower() in ('true', '1')
36
+
37
+ #
38
+ # ALLOWED_HOST protects the site against CSRF attacks.
39
+ # If DEBUG is set to False, you will need to configure this parameter,
40
+ # with the host you are using to serve GrimoireLab.
41
+ #
42
+ # https://docs.djangoproject.com/en/4.2/ref/settings/#allowed-hosts
43
+ #
44
+
45
+ if 'GRIMOIRELAB_ALLOWED_HOST' in os.environ:
46
+ ALLOWED_HOSTS = os.environ['GRIMOIRELAB_ALLOWED_HOST'].split(',')
47
+ else:
48
+ ALLOWED_HOSTS = [
49
+ '127.0.0.1',
50
+ 'localhost',
51
+ ]
52
+
53
+ #
54
+ # Cross-Origin Resource Sharing (CORS)
55
+ #
56
+ # You'll HAVE TO configure the origins that are authorized to make
57
+ # cross-site HTTP requests. Check the following link to understand
58
+ # the possibilities and parameters you can use.
59
+ #
60
+ # https://github.com/adamchainz/django-cors-headers#configuration
61
+ #
62
+
63
+ if 'GRIMOIRELAB_CORS_ALLOWED_ORIGINS' in os.environ:
64
+ CORS_ALLOWED_ORIGINS = os.environ['GRIMOIRELAB_CORS_ALLOWED_ORIGINS'].split(',')
65
+ elif 'GRIMOIRELAB_CORS_ALLOWED_ORIGIN_REGEXES' in os.environ:
66
+ CORS_ALLOWED_ORIGIN_REGEXES = os.environ['GRIMOIRELAB_CORS_ALLOWED_ORIGIN_REGEXES'].split(',')
67
+ else:
68
+ CORS_ALLOWED_ORIGINS = [
69
+ 'http://localhost:5173',
70
+ ]
71
+
72
+ CORS_ALLOW_CREDENTIALS = True
73
+
74
+
75
+ #
76
+ # The secret key must be a large random value and it must be kept secret.
77
+ #
78
+ # https://docs.djangoproject.com/en/4.2/ref/settings/#secret-key
79
+ #
80
+
81
+ SECRET_KEY = os.environ.get('GRIMOIRELAB_SECRET_KEY', 'fake-key')
82
+
83
+ #
84
+ # Application definition - DO NOT MODIFY
85
+ #
86
+
87
+ INSTALLED_APPS = [
88
+ 'django.contrib.admin',
89
+ 'django.contrib.auth',
90
+ 'django.contrib.contenttypes',
91
+ 'django.contrib.sessions',
92
+ 'django.contrib.messages',
93
+ 'django.contrib.staticfiles',
94
+ 'django_rq',
95
+ 'corsheaders',
96
+ 'rest_framework',
97
+ 'grimoirelab.core.scheduler',
98
+ 'grimoirelab.core.scheduler.tasks',
99
+ ]
100
+
101
+ MIDDLEWARE = [
102
+ 'corsheaders.middleware.CorsMiddleware',
103
+ 'django.middleware.security.SecurityMiddleware',
104
+ 'django.contrib.sessions.middleware.SessionMiddleware',
105
+ 'django.middleware.common.CommonMiddleware',
106
+ 'django.middleware.csrf.CsrfViewMiddleware',
107
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
108
+ 'django.contrib.messages.middleware.MessageMiddleware',
109
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
110
+ ]
111
+
112
+ ROOT_URLCONF = 'grimoirelab.core.app.urls'
113
+
114
+ TEMPLATES = [
115
+ {
116
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
117
+ 'DIRS': [BASE_DIR / 'templates'],
118
+ 'APP_DIRS': True,
119
+ 'OPTIONS': {
120
+ 'context_processors': [
121
+ 'django.template.context_processors.debug',
122
+ 'django.template.context_processors.request',
123
+ 'django.contrib.auth.context_processors.auth',
124
+ 'django.contrib.messages.context_processors.messages',
125
+ ],
126
+ },
127
+ },
128
+ ]
129
+
130
+ WSGI_APPLICATION = 'grimoirelab.core.app.wsgi.application'
131
+
132
+
133
+ #
134
+ # Grimoirelab core database
135
+ #
136
+ # You MUST set the database parameters in order to run
137
+ # GrimoireLab.
138
+ #
139
+
140
+ DATABASES = {
141
+ 'default': {
142
+ 'ENGINE': 'django.db.backends.mysql',
143
+ 'HOST': os.environ.get('GRIMOIRELAB_DB_HOST', '127.0.0.1'),
144
+ 'PORT': os.environ.get('GRIMOIRELAB_DB_PORT', 3306),
145
+ 'USER': os.environ.get('GRIMOIRELAB_DB_USER', 'root'),
146
+ 'PASSWORD': os.environ.get('GRIMOIRELAB_DB_PASSWORD', ''),
147
+ 'NAME': os.environ.get('GRIMOIRELAB_DB_DATABASE', 'grimoirelab_test'),
148
+ 'OPTIONS': {'charset': 'utf8mb4'},
149
+ }
150
+ }
151
+
152
+
153
+ # Password validation
154
+ # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
155
+
156
+ AUTH_PASSWORD_VALIDATORS = [
157
+ {
158
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
159
+ },
160
+ {
161
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
162
+ },
163
+ {
164
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
165
+ },
166
+ {
167
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
168
+ },
169
+ ]
170
+
171
+
172
+ #
173
+ # Internationalization
174
+ #
175
+ # https://docs.djangoproject.com/en/4.2/topics/i18n/
176
+ #
177
+ #
178
+
179
+ LANGUAGE_CODE = 'en-us'
180
+ USE_I18N = True
181
+
182
+ #
183
+ # Time Zone
184
+ #
185
+
186
+ USE_TZ = True
187
+ TIME_ZONE = 'UTC'
188
+
189
+ #
190
+ # GrimoireLab Logging
191
+ #
192
+ # https://docs.djangoproject.com/en/4.2/topics/logging/#configuring-logging
193
+ #
194
+
195
+ LOGGING = {
196
+ 'version': 1,
197
+ 'disable_existing_loggers': False,
198
+ 'formatters': {
199
+ 'simple': {
200
+ 'format': '[{asctime}] {message}',
201
+ 'style': '{',
202
+ },
203
+ 'verbose': {
204
+ 'format': '[{asctime} - {levelname} - {name}:{lineno}] {message}',
205
+ 'style': '{',
206
+ },
207
+ },
208
+ 'handlers': {
209
+ 'console': {
210
+ 'class': 'logging.StreamHandler',
211
+ 'formatter': 'verbose',
212
+ },
213
+ },
214
+ 'root': {
215
+ 'handlers': ['console'],
216
+ 'level': 'INFO',
217
+ },
218
+ }
219
+
220
+
221
+ #
222
+ # Static files (CSS, JavaScript, Images)
223
+ #
224
+ # https://docs.djangoproject.com/en/4.2/howto/static-files/
225
+ #
226
+
227
+ STATIC_URL = '/static/'
228
+ STATICFILES_DIRS = [
229
+ BASE_DIR / 'templates/static'
230
+ ]
231
+
232
+ # UI static files will be copied to the next path when
233
+ # 'collectstatic' is run.
234
+ # If you are serving these files in a dedicated server, you will
235
+ # need to copy them to their final destination.
236
+
237
+ STATIC_ROOT = os.path.join(BASE_DIR, 'static')
238
+
239
+ # MEDIA_URL is only needed when DEBUG is set to True.
240
+ # Modify this URL if you want to run the server in developer mode.
241
+
242
+ MEDIA_URL = 'http://media.localhost/'
243
+
244
+ #
245
+ # Default primary key field type
246
+ #
247
+ # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
248
+ #
249
+
250
+ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
251
+
252
+ # Django REST Framework settings
253
+
254
+ REST_FRAMEWORK = {
255
+ 'DEFAULT_RENDERER_CLASSES': (
256
+ 'rest_framework.renderers.JSONRenderer',
257
+ ),
258
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
259
+ 'PAGE_SIZE': 100
260
+ }
261
+
262
+ #
263
+ # GrimoireLab uses RQ to run background and async jobs.
264
+ # You'll HAVE TO set the next parameters in order to run
265
+ # them in the background.
266
+ #
267
+ # You can define the names of the RQ queues using the following
268
+ # environment variables.
269
+ #
270
+ # Take into account RQ uses Redis database. You have more
271
+ # info about these parameters on the following link:
272
+ #
273
+ # https://github.com/rq/django-rq
274
+ #
275
+
276
+ Q_PERCEVAL_JOBS = os.environ.get('GRIMOIRELAB_Q_PERCEVAL_JOBS', 'default')
277
+ Q_STORAGE_ITEMS = os.environ.get('GRIMOIRELAB_Q_STORAGE_ITEMS', 'items')
278
+ Q_EVENTS = os.environ.get('GRIMOIRELAB_Q_EVENTS', 'events')
279
+
280
+ _RQ_DATABASE = {
281
+ 'HOST': os.environ.get('GRIMOIRELAB_REDIS_HOST', '127.0.0.1'),
282
+ 'PORT': os.environ.get('GRIMOIRELAB_REDIS_PORT', 6379),
283
+ 'PASSWORD': os.environ.get('GRIMOIRELAB_REDIS_PASSWORD', ''),
284
+ 'DB': os.environ.get('GRIMOIRELAB_REDIS_DB', 0),
285
+ }
286
+
287
+ RQ_QUEUES = {
288
+ Q_PERCEVAL_JOBS: _RQ_DATABASE,
289
+ Q_STORAGE_ITEMS: _RQ_DATABASE,
290
+ Q_EVENTS: _RQ_DATABASE,
291
+ }
292
+
293
+ RQ = {
294
+ 'JOB_CLASS': 'grimoirelab.core.scheduler.jobs.GrimoireLabJob',
295
+ 'WORKER_CLASS': 'grimoirelab.core.scheduler.worker.GrimoireLabWorker',
296
+ }
297
+
298
+ #
299
+ # Task default configuration
300
+ #
301
+
302
+ GRIMOIRELAB_JOB_INTERVAL = int(os.environ.get('GRIMOIRELAB_JOB_INTERVAL', 60 * 60 * 2))
303
+ GRIMOIRELAB_JOB_MAX_RETRIES = int(os.environ.get('GRIMOIRELAB_JOB_MAX_RETRIES', 5))
304
+ GRIMOIRELAB_JOB_RESULT_TTL = int(os.environ.get('GRIMOIRELAB_JOB_RESULT_TTL', 300))
305
+ GRIMOIRELAB_JOB_TIMEOUT = int(os.environ.get('GRIMOIRELAB_JOB_TIMEOUT', -1))
306
+
307
+ GIT_STORAGE_PATH = os.environ.get('GRIMOIRELAB_GIT_PATH', '~/.perceval')
@@ -0,0 +1,77 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) GrimoireLab Contributors
4
+ #
5
+ # This program is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ from __future__ import annotations
20
+
21
+ import typing
22
+
23
+ from django.db.models import (
24
+ DateTimeField,
25
+ Model
26
+ )
27
+ from grimoirelab_toolkit.datetime import datetime_utcnow
28
+
29
+
30
+ if typing.TYPE_CHECKING:
31
+ import datetime
32
+
33
+
34
+ # Innodb and utf8mb4 can only index 191 characters
35
+ # For more information regarding this topic see:
36
+ # https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-conversion.html
37
+ MAX_SIZE_CHAR_INDEX = 191
38
+ MAX_SIZE_CHAR_FIELD = 128
39
+
40
+
41
+ class CreationDateTimeField(DateTimeField):
42
+ """Field automatically set to the current date when an object is created."""
43
+
44
+ def __init__(self, *args, **kwargs) -> None:
45
+ kwargs.setdefault('editable', False)
46
+ kwargs.setdefault('default', datetime_utcnow)
47
+ super().__init__(*args, **kwargs)
48
+
49
+
50
+ class LastModificationDateTimeField(DateTimeField):
51
+ """Field automatically set to the current date on each save() call."""
52
+
53
+ def __init__(self, *args, **kwargs) -> None:
54
+ kwargs.setdefault('editable', False)
55
+ kwargs.setdefault('default', datetime_utcnow)
56
+ super().__init__(*args, **kwargs)
57
+
58
+ def pre_save(self, model_instance: Model, add: bool) -> datetime.datetime:
59
+ value = datetime_utcnow()
60
+ setattr(model_instance, self.attname, value)
61
+ return value
62
+
63
+
64
+ class BaseModel(Model):
65
+ """Base abstract model for all models.
66
+
67
+ This model provides the common fields for all GrimoireLab Core
68
+ models.
69
+
70
+ The fields `created_at` and `last_modified` are automatically
71
+ set to the current date when the object is created or updated.
72
+ """
73
+ created_at = CreationDateTimeField()
74
+ last_modified = LastModificationDateTimeField()
75
+
76
+ class Meta:
77
+ abstract = True
File without changes
@@ -0,0 +1,72 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) GrimoireLab Contributors
4
+ #
5
+ # This program is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ from __future__ import annotations
20
+
21
+ import typing
22
+ import os
23
+
24
+ import click
25
+ import django.core.wsgi
26
+
27
+ from .commands.admin import admin
28
+ from .commands.run import run
29
+
30
+ if typing.TYPE_CHECKING:
31
+ from click import Context
32
+
33
+
34
+ @click.group()
35
+ @click.option('--config', 'cfg',
36
+ envvar='GRIMOIRELAB_CONFIG',
37
+ default='grimoirelab.core.config.settings',
38
+ show_default=True,
39
+ help="Configuration module in Python path syntax")
40
+ @click.pass_context
41
+ def grimoirelab(ctx: Context, cfg: str):
42
+ """Toolset for software development analytics.
43
+
44
+ GrimoireLab is a set of tools and a platform to retrieve, analyze,
45
+ and provide insights about data coming from software development
46
+ repositories. With this command, you'll be able to configure and
47
+ run its different services.
48
+
49
+ It requires to pass a configuration file module using
50
+ the Python path syntax (e.g. grimoirelab.core.config.settings).
51
+ Take into account the configuration should be accessible by your
52
+ PYTHON_PATH. You can also use the environment variable
53
+ GRIMOIRELAB_CONFIG to define the config location.
54
+ """
55
+ env = os.environ
56
+
57
+ if cfg:
58
+ env['DJANGO_SETTINGS_MODULE'] = cfg
59
+ ctx.ensure_object(dict)
60
+ ctx.obj["cfg"] = cfg
61
+ else:
62
+ raise click.ClickException(
63
+ "Configuration file not given. "
64
+ "Set it with '--config' option "
65
+ "or 'GRIMOIRELAB_CONFIG' env variable."
66
+ )
67
+
68
+ _ = django.core.wsgi.get_wsgi_application()
69
+
70
+
71
+ grimoirelab.add_command(admin)
72
+ grimoirelab.add_command(run)
File without changes
@@ -0,0 +1,163 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) GrimoireLab Contributors
4
+ #
5
+ # This program is free software; you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation; either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+
19
+ from __future__ import annotations
20
+
21
+ import typing
22
+
23
+ import click
24
+ import django.core
25
+ import django_rq
26
+
27
+ if typing.TYPE_CHECKING:
28
+ from click import Context
29
+
30
+
31
+ @click.group()
32
+ @click.pass_context
33
+ def admin(ctx: Context):
34
+ """GrimoireLab administration tool.
35
+
36
+ This swiss army knife tool allows to run administrative tasks to
37
+ configure, initialize, update, or check the service.
38
+ """
39
+ pass
40
+
41
+
42
+ @admin.command()
43
+ def setup():
44
+ """Run initialization tasks to configure GrimoireLab.
45
+
46
+ This command will create the database, tables and apply the
47
+ defined migrations and fixtures.
48
+ """
49
+ _setup()
50
+
51
+
52
+ def _setup():
53
+ """Setup GrimoireLab"""
54
+
55
+ _create_database()
56
+ _setup_database()
57
+ _install_static_files()
58
+
59
+ click.secho("\nGrimoirelab configuration completed", fg='bright_cyan')
60
+
61
+
62
+ def _create_database(database: str = 'default', db_name: str | None = None):
63
+ """Create an empty database"""
64
+
65
+ import MySQLdb
66
+ from django.conf import settings
67
+
68
+ db_params = settings.DATABASES[database]
69
+ db_name = db_name if db_name else db_params['NAME']
70
+
71
+ click.secho("## GrimoireLab database creation\n", fg='bright_cyan')
72
+
73
+ try:
74
+ cursor = MySQLdb.connect(
75
+ user=db_params['USER'],
76
+ password=db_params['PASSWORD'],
77
+ host=db_params['HOST'],
78
+ port=int(db_params['PORT'])
79
+ ).cursor()
80
+ cursor.execute(
81
+ f"CREATE DATABASE IF NOT EXISTS {db_name} "
82
+ "CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;"
83
+ )
84
+ except MySQLdb.DatabaseError as exc:
85
+ msg = f"Error creating database '{db_name}' for '{database}': {exc}."
86
+ raise click.ClickException(msg)
87
+
88
+ click.echo(f"GrimoireLab database '{db_name}' for '{database}' created.\n")
89
+
90
+
91
+ def _setup_database(database: str = 'default'):
92
+ """Apply migrations and fixtures to the database"""
93
+
94
+ click.secho(f"## GrimoireLab database setup for {database}\n",
95
+ fg='bright_cyan')
96
+
97
+ django.core.management.call_command('migrate', database=database)
98
+
99
+ click.echo()
100
+
101
+
102
+ def _install_static_files():
103
+ """Collect static files and install them."""
104
+
105
+ click.secho('## GrimoireLab static files installation\n',
106
+ fg='bright_cyan')
107
+
108
+ django.core.management.call_command('collectstatic',
109
+ ignore=['admin', 'rest_framework'],
110
+ clear=True,
111
+ interactive=False)
112
+
113
+ click.echo()
114
+
115
+
116
+ @admin.group()
117
+ @click.pass_context
118
+ def queues(ctx: Context):
119
+ """Manage the GrimoireLab job queues."""
120
+
121
+ pass
122
+
123
+
124
+ @queues.command(name='list')
125
+ def list_jobs():
126
+ """List the jobs in the queues.
127
+
128
+ Print the jobs classified by their status.
129
+ """
130
+ queue = django_rq.get_queue()
131
+
132
+ jobs = {
133
+ 'Started': [jid for jid in queue.started_job_registry.get_job_ids()],
134
+ 'Scheduled': [jid for jid in queue.scheduled_job_registry.get_job_ids()],
135
+ 'Failed': [jid for jid in queue.failed_job_registry.get_job_ids()],
136
+ 'Deferred': [jid for jid in queue.deferred_job_registry.get_job_ids()],
137
+ 'Canceled': [jid for jid in queue.canceled_job_registry.get_job_ids()],
138
+ 'Finished': [jid for jid in queue.finished_job_registry.get_job_ids()],
139
+ }
140
+ for key, value in jobs.items():
141
+ click.echo(key)
142
+ click.echo(value)
143
+ click.echo()
144
+
145
+
146
+ @queues.command(name='purge')
147
+ def remove_jobs():
148
+ """Remove the jobs from the queues."""
149
+
150
+ queue = django_rq.get_queue()
151
+
152
+ registries = {
153
+ 'Started': queue.failed_job_registry,
154
+ 'Scheduled': queue.finished_job_registry,
155
+ 'Failed': queue.scheduled_job_registry,
156
+ 'Deferred': queue.started_job_registry,
157
+ 'Canceled': queue.canceled_job_registry,
158
+ 'Finished': queue.deferred_job_registry
159
+ }
160
+ for key, registry in registries.items():
161
+ for jid in registry.get_job_ids():
162
+ registry.remove(jid)
163
+ click.echo(f"{key} registry removed.")