Compare commits

..

29 Commits

Author SHA1 Message Date
9424e21edc revert f02efab07a
All checks were successful
continuous-integration/drone/pr Build is passing
revert Merge pull request 'Update python Docker tag to v3.11.9' (!97) from renovate/minor-3.11-python into main

Reviewed-on: #97
2024-08-23 15:50:30 +02:00
f02efab07a Merge pull request 'Update python Docker tag to v3.11.9' (!97) from renovate/minor-3.11-python into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #97
2024-08-23 15:33:32 +02:00
08f1104942 Merge branch 'main' into renovate/minor-3.11-python
All checks were successful
continuous-integration/drone/pr Build is passing
2024-08-23 15:30:29 +02:00
8dde142f38 Try and use Python 3.11
Some checks failed
continuous-integration/drone/pr Build is failing
2024-08-23 15:26:39 +02:00
d1b1636aa2 Merge pull request 'Update dependency waitress to v2.1.2' (!93) from renovate/waitress-2.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #93
2024-08-23 12:01:59 +02:00
3fcd1fa20e Update python Docker tag to v3.11.9
Some checks failed
continuous-integration/drone/pr Build is failing
2024-08-22 18:37:13 +00:00
8394400e96 Update dependency waitress to v2.1.2
All checks were successful
continuous-integration/drone/pr Build is passing
2024-08-22 18:36:55 +00:00
843050f923 Merge pull request 'Update plugins/docker Docker tag to v20.18.4' (!95) from renovate/plugins-docker-20.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #95
2024-08-22 19:53:59 +02:00
812913ffe2 Merge pull request 'Update dependency reuse to v0.14.0' (!92) from renovate/reuse-0.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #92
2024-08-22 19:52:24 +02:00
dee80c7e14 Update plugins/docker Docker tag to v20.18.4
All checks were successful
continuous-integration/drone/pr Build is passing
2024-08-22 15:35:49 +00:00
de60ec0d46 Update dependency reuse to v0.14.0
All checks were successful
continuous-integration/drone/pr Build is passing
2024-08-22 14:35:41 +00:00
6c6fcea81c Merge pull request 'Update python Docker tag to v3.8.19' (!85) from renovate/patch-python into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #85
2024-08-22 13:17:55 +02:00
166e0d40c6 Merge pull request 'Update dependency werkzeug to v2.3.8' (!84) from renovate/werkzeug-2.x into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #84
2024-08-22 12:39:36 +02:00
c9b5ab62ed Update python Docker tag to v3.8.19
All checks were successful
continuous-integration/drone/pr Build is passing
2024-08-22 10:35:35 +00:00
c0005100b4 Update dependency werkzeug to v2.3.8
All checks were successful
continuous-integration/drone/pr Build is passing
2024-08-22 10:35:34 +00:00
d614039cdd Merge pull request 'Pin python Docker tag to 0ef73cd' (!83) from renovate/python into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #83
2024-08-22 11:52:25 +02:00
316236a7e5 Merge pull request 'Pin dependencies' (!82) from renovate/pin-dependencies into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #82
2024-08-22 11:51:54 +02:00
c15faabd6d Pinning werkzeug to v2
All checks were successful
continuous-integration/drone/pr Build is passing
Thanks for the hint in #78
2024-08-22 11:46:27 +02:00
53eab74e60 Pin python Docker tag to 0ef73cd
All checks were successful
continuous-integration/drone/pr Build is passing
2024-08-22 09:35:43 +00:00
82d908193c Pin dependencies
Some checks failed
continuous-integration/drone/pr Build is failing
2024-08-22 09:35:42 +00:00
6620a6819a Merge pull request 'Configure Renovate' (!81) from renovate/configure into main
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #81
2024-08-22 11:25:28 +02:00
be6fec18ab Update Renovate config
All checks were successful
continuous-integration/drone/pr Build is passing
2024-08-21 22:12:27 +02:00
9ebae03550 Add renovate.json
Some checks failed
continuous-integration/drone/pr Build is failing
2024-08-21 19:34:54 +00:00
763a6efc9f Merge pull request 'Improve Docker usage' (!80) from docker-improvements into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #80
2024-08-21 21:22:40 +02:00
6f04d23e6c Add license information to .dockerignore file
All checks were successful
continuous-integration/drone/pr Build is passing
2024-08-21 21:17:33 +02:00
155ddc556c Add .dockerignore file
Some checks failed
continuous-integration/drone/pr Build is failing
2024-08-21 21:09:12 +02:00
a5a85e6032 Fix Dockerfile warnings 2024-08-21 21:08:52 +02:00
6a3458a596 Pin Docker images in CI 2024-08-21 20:53:59 +02:00
97be8f4667 Ditch builder and base Docker images
This should make dependency upgrades easier and bring more clarity to the CI pipelines
2024-08-21 20:49:47 +02:00
15 changed files with 533 additions and 508 deletions

View File

@ -3,4 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
* *
!.gitignore !Pipfile
!Pipfile.lock
!data/
!ki/
!LICENSES/
!migrations/
!app.py
!run_prod.py

View File

@ -15,16 +15,18 @@ trigger:
steps: steps:
- name: install-lint-test - name: install-lint-test
image: git.wtf-eg.de/kompetenzinventar/builder:1.0.2 image: python:3.8.19-alpine@sha256:3bd7ea88cb637e09d6c7de24c5394657163a85c2be82bfebe0305cf07f8de1ea
env:
PYROOT: '/pyroot'
PYTHONUSERBASE: '/pyroot'
commands: commands:
- apk add --no-cache gcc g++ musl-dev python3-dev
- pip3 install pipenv
- pipenv install --dev - pipenv install --dev
- pipenv run flake8 - pipenv run flake8
- pipenv run reuse lint - pipenv run reuse lint
- pipenv run python -m unittest discover ki - pipenv run python -m unittest discover ki
image_pull_secrets:
- dockerconfig
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
@ -41,7 +43,7 @@ depends_on:
steps: steps:
- name: docker-publish - name: docker-publish
image: plugins/docker image: plugins/docker:20.18.4@sha256:a8d3d86853c721492213264815f1d00d3ed13f42f5c1855a02f47fa4d5f1e042
settings: settings:
registry: git.wtf-eg.de registry: git.wtf-eg.de
repo: git.wtf-eg.de/kompetenzinventar/backend repo: git.wtf-eg.de/kompetenzinventar/backend
@ -68,7 +70,7 @@ depends_on:
steps: steps:
- name: deploy-dev - name: deploy-dev
image: appleboy/drone-ssh image: appleboy/drone-ssh:1.7.5@sha256:995677e073454912f26d4c0fdd2f9df2e1f5a30d6603d3f2ece667311b6babb3
settings: settings:
host: host:
- dev01.wtf-eg.net - dev01.wtf-eg.net
@ -91,14 +93,19 @@ trigger:
steps: steps:
- name: install-lint-test - name: install-lint-test
image: git.wtf-eg.de/kompetenzinventar/builder:1.0.2 image: python:3.8.19-alpine@sha256:3bd7ea88cb637e09d6c7de24c5394657163a85c2be82bfebe0305cf07f8de1ea
env:
PYROOT: '/pyroot'
PYTHONUSERBASE: '/pyroot'
commands: commands:
- apk add --no-cache gcc g++ musl-dev python3-dev
- pip3 install pipenv
- pipenv install --dev - pipenv install --dev
- pipenv run flake8 - pipenv run flake8
- pipenv run reuse lint - pipenv run reuse lint
- pipenv run python -m unittest discover ki - pipenv run python -m unittest discover ki
- name: docker-publish - name: docker-publish
image: plugins/docker image: plugins/docker:20.18.4@sha256:a8d3d86853c721492213264815f1d00d3ed13f42f5c1855a02f47fa4d5f1e042
settings: settings:
registry: git.wtf-eg.de registry: git.wtf-eg.de
repo: git.wtf-eg.de/kompetenzinventar/backend repo: git.wtf-eg.de/kompetenzinventar/backend

View File

@ -10,3 +10,7 @@ License: MIT
Files: Pipfile.lock migrations/* Files: Pipfile.lock migrations/*
Copyright: WTF Kooperative eG <https://wtf-eg.de/> Copyright: WTF Kooperative eG <https://wtf-eg.de/>
License: AGPL-3.0-or-later License: AGPL-3.0-or-later
Files: renovate.json
Copyright: WTF Kooperative eG <https://wtf-eg.de/>
License: AGPL-3.0-or-later

View File

@ -2,7 +2,17 @@
# #
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
FROM git.wtf-eg.de/kompetenzinventar/builder:1.0.2 as builder FROM python:3.8.19-alpine@sha256:3bd7ea88cb637e09d6c7de24c5394657163a85c2be82bfebe0305cf07f8de1ea AS builder
ENV PYROOT=/pyroot
ENV PYTHONUSERBASE=$PYROOT
RUN apk add --no-cache \
gcc \
g++ \
musl-dev \
python3-dev && \
pip3 install pipenv
COPY Pipfile* ./ COPY Pipfile* ./
@ -10,7 +20,10 @@ RUN PIP_USER=1 PIP_IGNORE_INSTALLED=1 pipenv install --system --deploy --ignore-
RUN pip3 uninstall --yes pipenv RUN pip3 uninstall --yes pipenv
FROM git.wtf-eg.de/kompetenzinventar/base:1.0.2 as ki-backend FROM python:3.8.19-alpine@sha256:3bd7ea88cb637e09d6c7de24c5394657163a85c2be82bfebe0305cf07f8de1ea AS ki-backend
ENV PYROOT=/pyroot
ENV PYTHONUSERBASE=$PYROOT
# Install six explicitly. Otherwise Python complains about it missing. # Install six explicitly. Otherwise Python complains about it missing.
RUN pip3 install six RUN pip3 install six

34
Pipfile
View File

@ -8,26 +8,26 @@ verify_ssl = true
name = "pypi" name = "pypi"
[packages] [packages]
flask = "~=3.0.0" flask = "==2.0.3"
python-dotenv = "~=0.17.1" python-dotenv = "==0.17.1"
flask-migrate = "~=4.0.5" flask-migrate = "==3.0.1"
flask-sqlalchemy = "~=3.1.1" flask-sqlalchemy = "==2.5.1"
sqlalchemy = "~=2.0.25" sqlalchemy = "==1.4.53"
waitress = "~=2.1.2" waitress = "==2.1.2"
pyyaml = "~=6.0.1" pyyaml = "==6.0.2"
flask-cors = "~=4.0.0" flask-cors = "==3.0.10"
ldap3 = "~=2.9.1" ldap3 = "==2.9.1"
pymysql = "~=1.1.0" pymysql = "==1.0.3"
werkzeug = "~=3.0.1" werkzeug = "==2.3.8"
[dev-packages] [dev-packages]
flake8 = "~=3.9.2" flake8 = "==3.9.2"
yapf = "~=0.40.2" yapf = "==0.31.0"
pre-commit = "~=2.13.0" pre-commit = "==2.13.0"
reuse = "~=0.13.0" reuse = "==0.14.0"
[requires] [requires]
python_version = "3.11" python_version = "3.8"
[scripts] [scripts]
clean = "rm storage/ki.sqlite" clean = "rm data/ki.sqlite"

815
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

2
app.py
View File

@ -38,7 +38,7 @@ app.config["KI_LDAP_AUTH_PASSWORD"] = os.getenv("KI_LDAP_AUTH_PASSWORD")
app.config["KI_LDAP_BASE_DN"] = os.getenv("KI_LDAP_BASE_DN") app.config["KI_LDAP_BASE_DN"] = os.getenv("KI_LDAP_BASE_DN")
CORS(app) CORS(app)
db = SQLAlchemy(app, session_options={"future": True}) db = SQLAlchemy(app)
migrate = Migrate(app, db, compare_type=True) migrate = Migrate(app, db, compare_type=True)
app.logger.info("Hello from KI") app.logger.info("Hello from KI")

View File

@ -2,7 +2,7 @@
# #
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
SQLALCHEMY_DATABASE_URI=sqlite:///../storage/ki.sqlite SQLALCHEMY_DATABASE_URI=sqlite:///storage/ki.sqlite
CORS_ORIGINS=* CORS_ORIGINS=*

View File

@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/> # SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
import csv import csv
@ -18,7 +19,7 @@ def seed_contacttypes():
for contacttype in csv_reader: for contacttype in csv_reader:
id = int(contacttype["id"]) id = int(contacttype["id"])
db_contacttype = db.session.get(ContactType, id) db_contacttype = ContactType.query.get(id)
if db_contacttype is None: if db_contacttype is None:
db.session.add(ContactType(id=int(contacttype["id"]), name=contacttype["name"])) db.session.add(ContactType(id=int(contacttype["id"]), name=contacttype["name"]))
@ -98,7 +99,7 @@ def seed(dev: bool):
for skill in skills_csv_reader: for skill in skills_csv_reader:
id = int(skill["id"]) id = int(skill["id"])
db_skill = db.session.get(Skill, id) db_skill = Skill.query.get(id)
if db_skill is None: if db_skill is None:
db.session.add(Skill(id=int(skill["id"]), name=skill["name"])) db.session.add(Skill(id=int(skill["id"]), name=skill["name"]))
@ -112,7 +113,7 @@ def seed(dev: bool):
for iso in iso_csv_reader: for iso in iso_csv_reader:
id = iso["639-1"] id = iso["639-1"]
db_language = db.session.get(Language, id) db_language = Language.query.get(id)
if db_language is None: if db_language is None:
db.session.add(Language(id=iso["639-1"], name=iso["Sprache"])) db.session.add(Language(id=iso["639-1"], name=iso["Sprache"]))
@ -146,13 +147,12 @@ def seed(dev: bool):
freetext="1001010010111!!!", freetext="1001010010111!!!",
skills=[(1, 5)], skills=[(1, 5)],
address=("Friedrich Witzig", "", "", "", "", "", "")) address=("Friedrich Witzig", "", "", "", "", "", ""))
# all_skills = [(skill.id, 3) for skill in Skill.query.all()] # query causes problems
# seed_user("jutta", languages=[("fr", 5)], skills=all_skills) all_skills = [(skill.id, 3) for skill in Skill.query.all()]
seed_user("jutta", languages=[("fr", 5)], skills=all_skills)
seed_user("giesela", skills=[(9, 3), (10, 5)]) seed_user("giesela", skills=[(9, 3), (10, 5)])
seed_user("bertha", visible=False, skills=[(11, 3), (10, 5)]) seed_user("bertha", visible=False, skills=[(11, 3), (10, 5)])
seed_user("monique", languages=[("fr", 4)]) seed_user("monique", languages=[("fr", 4)])
print("seeding done")
with app.app_context(): db.session.commit()
db.session.commit() # also problematic
print("commit done")

View File

@ -33,7 +33,7 @@ def update_languages(profile, languages_data):
if "id" not in language_data["language"]: if "id" not in language_data["language"]:
continue continue
language = db.session.get(Language, language_data["language"]["id"]) language = Language.query.get(language_data["language"]["id"])
profile_language = ProfileLanguage.query.filter(ProfileLanguage.profile == profile, profile_language = ProfileLanguage.query.filter(ProfileLanguage.profile == profile,
ProfileLanguage.language == language).first() ProfileLanguage.language == language).first()
@ -110,7 +110,7 @@ def update_contacts(profile, contacts_data):
if "id" in contact_data: if "id" in contact_data:
contact_id = int(contact_data["id"]) contact_id = int(contact_data["id"])
contact_ids_to_be_deleted.remove(contact_id) contact_ids_to_be_deleted.remove(contact_id)
contact = db.session.get(Contact, contact_id) contact = Contact.query.get(contact_id)
else: else:
contact = Contact(profile=profile, contacttype=contacttype) contact = Contact(profile=profile, contacttype=contacttype)
db.session.add(contact) db.session.add(contact)
@ -122,7 +122,7 @@ def update_contacts(profile, contacts_data):
def update_profile(user_id: int): def update_profile(user_id: int):
user = db.session.get(User, user_id) user = User.query.get(user_id)
if user is None: if user is None:
return make_response({}, 404) return make_response({}, 404)

View File

@ -10,14 +10,13 @@ from ki.auth import auth
from ki.handlers import find_profiles as find_profiles_handler from ki.handlers import find_profiles as find_profiles_handler
from ki.handlers import update_profile as update_profile_handler from ki.handlers import update_profile as update_profile_handler
from ki.models import ContactType, Language, Skill, Token, User from ki.models import ContactType, Language, Skill, Token, User
from app import app, db from app import app
content_type_svg = "image/svg+xml" content_type_svg = "image/svg+xml"
content_type_png = "image/png" content_type_png = "image/png"
def token_auth(func): def token_auth(func):
@wraps(func) @wraps(func)
def _token_auth(*args, **kwargs): def _token_auth(*args, **kwargs):
auth_header = request.headers.get("Authorization") auth_header = request.headers.get("Authorization")
@ -66,7 +65,7 @@ def handle_completion_request(model, key):
def handle_icon_request(model, id, path): def handle_icon_request(model, id, path):
object = db.session.get(model, id) object = model.query.get(id)
if object is None: if object is None:
return make_response({}, 404) return make_response({}, 404)

View File

@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/> # SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
from alembic import command from alembic import command
@ -8,14 +9,12 @@ import unittest
from app import app, db, migrate from app import app, db, migrate
from ki.actions import seed from ki.actions import seed
from ki.models import Skill from ki.models import Skill
from sqlalchemy import select
class ApiTest(unittest.TestCase): class ApiTest(unittest.TestCase):
maxDiff = None maxDiff = None
def setUp(self): def setUp(self):
print("Running setup")
app.debug = True app.debug = True
app.config["KI_AUTH"] = "file" app.config["KI_AUTH"] = "file"
app.config["TESTING"] = True app.config["TESTING"] = True
@ -23,38 +22,19 @@ class ApiTest(unittest.TestCase):
self.client = app.test_client() self.client = app.test_client()
config = migrate.get_config()
with app.app_context(): with app.app_context():
config = migrate.get_config()
command.upgrade(config, "head") command.upgrade(config, "head")
seed(True)
# statement = select(Skill).order_by(Skill.id.desc()) seed(True)
# print(statement) max_skill = Skill.query.order_by(Skill.id.desc()).first()
# skill_obj = db.session.scalars(statement).all()
# print(skill_obj)
# statement = select(Skill.id)
# print(statement)
# max_skill = db.session.Skill().order_by(Skill.id.desc()).first()
# max_skill = Skill.query.order_by(Skill.id.desc()).first() # TODO: problematic
with db.session.no_autoflush: # only works on first test run
max_skill = db.session.query(Skill).order_by(Skill.id.desc()).first() # TODO: also problematic,
# skills = db.session.execute(db.select(Skill)).scalars()
# print(max_skill)
# max_skill = db.session.execute(db.select(Skill)
# .order_by(Skill.id.desc())
# ).scalar_one()
print(max_skill)
print("max_skill done")
self.max_skill_id = max_skill.id self.max_skill_id = max_skill.id
def tearDown(self): def tearDown(self):
print("Running teardown")
with app.app_context():
db.drop_all() db.drop_all()
db.engine.dispose() db.engine.dispose()
def login(self, username, password): def login(self, username, password):
# with app.app_context():
login_data = {"username": username, "password": password} login_data = {"username": username, "password": password}
login_response = self.client.post("/users/login", data=json.dumps(login_data), content_type="application/json") login_response = self.client.post("/users/login", data=json.dumps(login_data), content_type="application/json")

View File

@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/> # SPDX-FileCopyrightText: WTF Kooperative eG <https://wtf-eg.de/>
#
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
import unittest import unittest
@ -8,38 +9,35 @@ from ki.test.ApiTest import ApiTest
class TestContactTypesEndpoint(ApiTest): class TestContactTypesEndpoint(ApiTest):
def test_skills_options(self): def test_skills_options(self):
print("test_skills_options")
# with app.app_context():
response = self.client.options("/contacttypes") response = self.client.options("/contacttypes")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn("Access-Control-Allow-Origin", response.headers) self.assertIn("Access-Control-Allow-Origin", response.headers)
self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*") self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*")
# def test_get_contacttypes_unauthorised(self): def test_get_contacttypes_unauthorised(self):
# print("test_get_contacttypes_unauthorised") response = self.client.get("/contacttypes?search=m")
# response = self.client.get("/contacttypes?search=m") self.assertEqual(response.status_code, 401)
# self.assertEqual(response.status_code, 401)
# def test_get_contacttypes(self): def test_get_contacttypes(self):
# token = self.login("peter", "geheim")["token"] token = self.login("peter", "geheim")["token"]
# response = self.client.get("/contacttypes?search=m", headers={"Authorization": "Bearer " + token}) response = self.client.get("/contacttypes?search=m", headers={"Authorization": "Bearer " + token})
# self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# self.assertEqual( self.assertEqual(
# { {
# "contacttypes": [{ "contacttypes": [{
# "id": 5, "id": 5,
# "name": "Mastodon" "name": "Mastodon"
# }, { }, {
# "id": 4, "id": 4,
# "name": "Matrix" "name": "Matrix"
# }, { }, {
# "id": 2, "id": 2,
# "name": "Mobiltelefon" "name": "Mobiltelefon"
# }] }]
# }, response.json) }, response.json)
# self.assertIn("Access-Control-Allow-Origin", response.headers) self.assertIn("Access-Control-Allow-Origin", response.headers)
# self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*") self.assertEqual(response.headers["Access-Control-Allow-Origin"], "*")
if __name__ == "main": if __name__ == "main":

View File

@ -19,7 +19,7 @@ logger = logging.getLogger('alembic.env')
# target_metadata = mymodel.Base.metadata # target_metadata = mymodel.Base.metadata
config.set_main_option( config.set_main_option(
'sqlalchemy.url', 'sqlalchemy.url',
str(current_app.extensions['migrate'].db.engine.url).replace( str(current_app.extensions['migrate'].db.get_engine().url).replace(
'%', '%%')) '%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata target_metadata = current_app.extensions['migrate'].db.metadata
@ -68,7 +68,7 @@ def run_migrations_online():
directives[:] = [] directives[:] = []
logger.info('No changes in schema detected.') logger.info('No changes in schema detected.')
connectable = current_app.extensions['migrate'].db.engine connectable = current_app.extensions['migrate'].db.get_engine()
with connectable.connect() as connection: with connectable.connect() as connection:
context.configure( context.configure(

18
renovate.json Normal file
View File

@ -0,0 +1,18 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:best-practices",
":disableDependencyDashboard",
":maintainLockFilesMonthly",
":pinVersions",
":separateMultipleMajorReleases"
],
"packageRules": [
{
"matchDepNames": ["python"],
"groupName": "Python",
"separateMinorPatch": true,
"separateMultipleMinor": true
}
]
}