Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,13 @@ default = true
# WYCOFAĆ gdy upstream wyda release z #2156 → wróć na PyPI `>=4.5`. Tracking: #280.
[tool.uv.sources]
django-import-export = { git = "https://github.com/mpasternak/django-import-export.git", rev = "d6ee0d39194fee31437affbdc6fee5ce549b4b8f" }
# TYMCZASOWO: pbn-client 0.3.0 (ErrorRecord) nie jest jeszcze na PyPI. Pin do
# TYMCZASOWO: pbn-client 0.2.1 (ErrorRecord) nie jest jeszcze na PyPI. Pin do
# commita gałęzi feat/error-record, żeby CI/lokalnie rozwiązać zależność.
# USUŃ po wydaniu pbn-client 0.3.0 na PyPI (wtedy sam pin >=0.3,<0.4 wystarczy).
# USUŃ po wydaniu pbn-client 0.2.1 na PyPI (wtedy sam pin >=0.2.1,<0.3 wystarczy).
pbn-client = { git = "https://github.com/iplweb/pbn-client.git", rev = "84503cf41b308bc86edc6f8709b71383a8676cac" }
# TYMCZASOWO: django-pbn-client 0.2.1 (sync_dictionary, D3) nie jest jeszcze na
# PyPI. Pin do commita gałęzi feat/sync-dictionary. USUŃ po wydaniu 0.2.1.
django-pbn-client = { git = "https://github.com/iplweb/django-pbn-client.git", rev = "9b2164f889861db9292b91508debf0732211bb39" }

# Konfiguracja `pytest-testcontainers-django` — pluginu pytest, ktory
# startuje kontenery PG/Redis przed importem Django settings i wstrzykuje
Expand Down
5 changes: 5 additions & 0 deletions src/bpp/newsfragments/pbn-d3-sync-dictionary.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Synchronizacja słownika dyscyplin z PBN nie trzyma już otwartej transakcji
bazodanowej przez cały czas pobierania danych z PBN. Pobranie (remote) wykonuje
się teraz przed otwarciem transakcji (wzorzec ``sync_dictionary`` z pakietu
``django-pbn-client``), a zapis leci atomowo — dłuższa niedostępność PBN nie
blokuje już połączenia bazodanowego.
47 changes: 39 additions & 8 deletions src/pbn_api/client/disciplines.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
"""Disciplines synchronization mixin for PBN API client."""
"""Disciplines synchronization mixin for PBN API client.

Pobranie słownika z PBN korzysta z ``django_pbn_client.sync_dictionary``
(materialize-before-atomic): remote-fetch wykonuje się PRZED transakcją, a
upsert do lokalnych modeli — w świeżym bloku atomic. Wcześniej
``@transaction.atomic`` obejmował cały remote-call.
"""

from django.db import transaction
from django_pbn_client import sync_dictionary

from import_common.core import (
matchuj_aktualna_dyscypline_pbn,
Expand All @@ -14,11 +21,21 @@
class DisciplinesMixin:
"""Mixin providing discipline synchronization methods."""

@transaction.atomic
def download_disciplines(self):
"""Zapisuje słownik dyscyplin z API PBN do lokalnej bazy"""
"""Pobierz słownik dyscyplin z API PBN i zapisz do lokalnej bazy.

Remote-fetch (``get_disciplines``) wykonuje się poza transakcją; sam
zapis leci atomowo (patrz ``sync_dictionary``).
"""
sync_dictionary(self.get_disciplines, self._upsert_disciplines)

def _upsert_disciplines(self, elems):
"""Upsert pobranego słownika do ``DisciplineGroup``/``Discipline``.

for elem in self.get_disciplines():
Wołane WEWNĄTRZ transakcji otwartej przez ``sync_dictionary`` — bez
własnego ``@transaction.atomic``.
"""
for elem in elems:
validityDateFrom = elem.get("validityDateFrom", None)
validityDateTo = elem.get("validityDateTo", None)
uuid = elem["uuid"]
Expand All @@ -43,9 +60,25 @@ def download_disciplines(self):
),
)

@transaction.atomic
def sync_disciplines(self):
"""Pobierz słownik i zsynchronizuj tłumaczenia dyscyplin BPP.

Remote-fetch (``download_disciplines``) jest transakcyjnie bezpieczny;
dopasowanie do modeli BPP leci w OSOBNEJ transakcji
(``_sync_discipline_translations``). Remote-call NIE jest już
obejmowany transakcją (wcześniejszy ``@transaction.atomic`` na całej
metodzie trzymał ją otwartą przez czas pobierania z PBN).
"""
self.download_disciplines()
self._sync_discipline_translations()

@transaction.atomic
def _sync_discipline_translations(self):
"""Dopasuj aktualny słownik PBN do modeli BPP.

Matching (``Dyscyplina_Naukowa``/``TlumaczDyscyplin``) jest BPP-specific
i celowo pozostaje w BPP (nie w pakiecie).
"""
try:
cur_dg = DisciplineGroup.objects.get_current()
except DisciplineGroup.DoesNotExist as e:
Expand Down Expand Up @@ -76,9 +109,7 @@ def sync_disciplines(self):
wpis_tlumacza.save()

for discipline in cur_dg.discipline_set.all():
if discipline.name == "weterynaria":
pass
# Każda dyscyplina z aktualnego słownika powinna być wpisana do systemu BPP
# Każda dyscyplina z aktualnego słownika powinna być wpisana do BPP
try:
TlumaczDyscyplin.objects.get(pbn_2024_now=discipline)
except TlumaczDyscyplin.DoesNotExist:
Expand Down
52 changes: 41 additions & 11 deletions src/pbn_api/tests/test_client_disciplines.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pathlib import Path

import pytest
from django.db import connection
from pbn_client.const import PBN_GET_DISCIPLINES_URL

from bpp.decorators import json
Expand All @@ -17,23 +18,23 @@
from pbn_api.models.discipline import Discipline


def test_get_disciplines(pbn_client):
def _load_disciplines_fixture(pbn_client):
fixture_path = Path(__file__).parent / "fixture_test_get_disciplines.json"
with open(fixture_path, "rb") as f:
pbn_client.transport.return_values[PBN_GET_DISCIPLINES_URL] = json.loads(
f.read()
)


def test_get_disciplines(pbn_client):
_load_disciplines_fixture(pbn_client)
ret = pbn_client.get_disciplines()
assert "validityDateFrom" in ret[0]


@pytest.mark.django_db
def test_download_disciplines(pbn_client):
fixture_path = Path(__file__).parent / "fixture_test_get_disciplines.json"
with open(fixture_path, "rb") as f:
pbn_client.transport.return_values[PBN_GET_DISCIPLINES_URL] = json.loads(
f.read()
)
_load_disciplines_fixture(pbn_client)

assert Discipline.objects.count() == 0
pbn_client.download_disciplines()
Expand All @@ -42,11 +43,7 @@ def test_download_disciplines(pbn_client):

@pytest.mark.django_db
def test_sync_disciplines(pbn_client):
fixture_path = Path(__file__).parent / "fixture_test_get_disciplines.json"
with open(fixture_path, "rb") as f:
pbn_client.transport.return_values[PBN_GET_DISCIPLINES_URL] = json.loads(
f.read()
)
_load_disciplines_fixture(pbn_client)

d1 = Dyscyplina_Naukowa.objects.create(kod="5.1", nazwa="ekonomia i finanse")
d2 = Dyscyplina_Naukowa.objects.create(kod="1.6", nazwa="nauka o kulturze")
Expand All @@ -60,3 +57,36 @@ def test_sync_disciplines(pbn_client):
assert TlumaczDyscyplin.objects.przetlumacz_dyscypline(d1, 2024) is not None

assert TlumaczDyscyplin.objects.przetlumacz_dyscypline(d2, 2024) is not None


@pytest.mark.django_db(transaction=True)
def test_download_disciplines_fetches_outside_transaction(pbn_client):
"""D3/bugfix: remote-fetch (get_disciplines) NIE może dziać się w otwartej
transakcji (wcześniej @transaction.atomic obejmował cały remote-call).

``transaction=True`` sprawia, że sam test nie owija się w atomic, więc
``connection.in_atomic_block`` w trakcie fetchu wiarygodnie odzwierciedla
brak transakcji; upsert leci już wewnątrz transakcji sync_dictionary.
"""
_load_disciplines_fixture(pbn_client)

seen = {}
original_get = pbn_client.get_disciplines

def spying_get_disciplines():
seen["fetch_in_atomic"] = connection.in_atomic_block
return original_get()

original_upsert = pbn_client._upsert_disciplines

def spying_upsert(elems):
seen["upsert_in_atomic"] = connection.in_atomic_block
return original_upsert(elems)

pbn_client.get_disciplines = spying_get_disciplines
pbn_client._upsert_disciplines = spying_upsert

pbn_client.download_disciplines()

assert seen["fetch_in_atomic"] is False # remote POZA transakcją
assert seen["upsert_in_atomic"] is True # zapis WEWNĄTRZ transakcji
10 changes: 3 additions & 7 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading