PgGraph Π΅ ΠΏΠΎΠΌΠΎΡ‰Π½Π° ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠ° Π·Π° Π°Ρ€Ρ…ΠΈΠ²ΠΈΡ€Π°Π½Π΅ ΠΈ Π½Π°ΠΌΠΈΡ€Π°Π½Π΅ Π½Π° зависимости Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ Π² PostgreSQL

PgGraph Π΅ ΠΏΠΎΠΌΠΎΡ‰Π½Π° ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠ° Π·Π° Π°Ρ€Ρ…ΠΈΠ²ΠΈΡ€Π°Π½Π΅ ΠΈ Π½Π°ΠΌΠΈΡ€Π°Π½Π΅ Π½Π° зависимости Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ Π² PostgreSQL
ДнСс искам Π΄Π° прСдставя Π½Π° Ρ‡ΠΈΡ‚Π°Ρ‚Π΅Π»ΠΈΡ‚Π΅ Π½Π° Habr ΠΏΠΎΠΌΠΎΡ‰Π½Π° ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠ°, написана Π½Π° Python Π·Π° Ρ€Π°Π±ΠΎΡ‚Π° със зависимости Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ Π² Π‘Π£Π‘Π” PostgreSQL.

API Π½Π° ΠΏΠΎΠΌΠΎΡ‰Π½Π°Ρ‚Π° ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠ° Π΅ прост ΠΈ сС ΡΡŠΡΡ‚ΠΎΠΈ ΠΎΡ‚ Ρ‚Ρ€ΠΈ ΠΌΠ΅Ρ‚ΠΎΠ΄Π°:

  • Π°Ρ€Ρ…ΠΈΠ²Π½Π°_Ρ‚Π°Π±Π»ΠΈΡ†Π° - рСкурсивно Π°Ρ€Ρ…ΠΈΠ²ΠΈΡ€Π°Π½Π΅/ΠΈΠ·Ρ‚Ρ€ΠΈΠ²Π°Π½Π΅ Π½Π° Ρ€Π΅Π΄ΠΎΠ²Π΅ с ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈ ΠΏΡŠΡ€Π²ΠΈΡ‡Π½ΠΈ ΠΊΠ»ΡŽΡ‡ΠΎΠ²Π΅
  • get_table_references β€” Ρ‚ΡŠΡ€ΡΠ΅Π½Π΅ Π½Π° зависимости Π·Π° Ρ‚Π°Π±Π»ΠΈΡ†Π° (Ρ‰Π΅ ΠΏΠΎΠΊΠ°ΠΆΠ΅ Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ, към ΠΊΠΎΠΈΡ‚ΠΎ сС ΠΏΠΎΠ·ΠΎΠ²Π°Π²Π° ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π°Ρ‚Π° ΠΈ Ρ‚Π΅Π·ΠΈ, ΠΊΠΎΠΈΡ‚ΠΎ я ΠΏΡ€Π΅ΠΏΡ€Π°Ρ‰Π°Ρ‚)
  • get_rows_references - Ρ‚ΡŠΡ€ΡΠ΅Π½Π΅ Π½Π° Ρ€Π΅Π΄ΠΎΠ²Π΅ Π² Π΄Ρ€ΡƒΠ³ΠΈ Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ, ΠΊΠΎΠΈΡ‚ΠΎ ΠΏΡ€Π΅ΠΏΡ€Π°Ρ‰Π°Ρ‚ към ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½ΠΈ Ρ€Π΅Π΄ΠΎΠ²Π΅ Π² ΠΆΠ΅Π»Π°Π½Π°Ρ‚Π° Ρ‚Π°Π±Π»ΠΈΡ†Π°

праистория

Казвам сС ОлСг Π‘ΠΎΡ€Π·ΠΎΠ², Π°Π· съм Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ Π² CRM Π΅ΠΊΠΈΠΏΠ° Π·Π° ΠΌΠ΅Π½ΠΈΠ΄ΠΆΡŠΡ€ΠΈ ΠΏΠΎ ΠΈΠΏΠΎΡ‚Π΅Ρ‡Π½ΠΎ ΠΊΡ€Π΅Π΄ΠΈΡ‚ΠΈΡ€Π°Π½Π΅ Π² Domklik.

ΠžΡΠ½ΠΎΠ²Π½Π°Ρ‚Π° Π±Π°Π·Π° Π΄Π°Π½Π½ΠΈ Π½Π° Π½Π°ΡˆΠ°Ρ‚Π° CRM систСма Π΅ Π΅Π΄Π½Π° ΠΎΡ‚ Π½Π°ΠΉ-Π³ΠΎΠ»Π΅ΠΌΠΈΡ‚Π΅ ΠΏΠΎ ΠΎΠ±Π΅ΠΌ Π² компанията. ОсвСн Ρ‚ΠΎΠ²Π° Π΅ ΠΈ Π΅Π΄ΠΈΠ½ ΠΎΡ‚ Π½Π°ΠΉ-старитС: появи сС ΠΏΡ€ΠΈ самото стартиранС Π½Π° ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°, ΠΊΠΎΠ³Π°Ρ‚ΠΎ Π΄ΡŠΡ€Π²Π΅Ρ‚Π°Ρ‚Π° бяха Π³ΠΎΠ»Π΅ΠΌΠΈ, Domklik бСшС ΡΡ‚Π°Ρ€Ρ‚ΡŠΠΏ ΠΈ вмСсто микроуслуга Π½Π° ΠΌΠΎΠ΄Π΅Ρ€Π½Π° Python асинхронна Ρ€Π°ΠΌΠΊΠ° имашС ΠΎΠ³Ρ€ΠΎΠΌΠ΅Π½ ΠΌΠΎΠ½ΠΎΠ»ΠΈΡ‚ Π² PHP.

ΠŸΡ€Π΅Ρ…ΠΎΠ΄ΡŠΡ‚ ΠΎΡ‚ PHP към Python бСшС ΠΌΠ½ΠΎΠ³ΠΎ дълъг ΠΈ изисквашС Π΅Π΄Π½ΠΎΠ²Ρ€Π΅ΠΌΠ΅Π½Π½Π° ΠΏΠΎΠ΄Π΄Ρ€ΡŠΠΆΠΊΠ° Π½Π° Π΄Π²Π΅Ρ‚Π΅ систСми, ΠΊΠΎΠ΅Ρ‚ΠΎ сС ΠΎΡ‚Ρ€Π°Π·ΠΈ Π½Π° Π΄ΠΈΠ·Π°ΠΉΠ½Π° Π½Π° Π±Π°Π·Π°Ρ‚Π° Π΄Π°Π½Π½ΠΈ.

Π’ Ρ€Π΅Π·ΡƒΠ»Ρ‚Π°Ρ‚ Π½Π° Ρ‚ΠΎΠ²Π° ΠΈΠΌΠ°ΠΌΠ΅ Π±Π°Π·Π° Π΄Π°Π½Π½ΠΈ с голям Π±Ρ€ΠΎΠΉ силно ΡΠ²ΡŠΡ€Π·Π°Π½ΠΈ ΠΈ ΠΎΠ³Ρ€ΠΎΠΌΠ½ΠΈ Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ с ΠΊΡƒΠΏ индСкси Π·Π° Ρ€Π°Π·Π»ΠΈΡ‡Π½ΠΈ Ρ‚ΠΈΠΏΠΎΠ²Π΅ заявки. Всичко Ρ‚ΠΎΠ²Π° сС отразява Π½Π΅Π³Π°Ρ‚ΠΈΠ²Π½ΠΎ Π½Π° производитСлността Π½Π° Π±Π°Π·Π°Ρ‚Π° Π΄Π°Π½Π½ΠΈ: ΠΏΠΎΡ€Π°Π΄ΠΈ Π³ΠΎΠ»Π΅ΠΌΠΈΡ‚Π΅ Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ ΠΈ ΠΊΡƒΠΏ Π²Ρ€ΡŠΠ·ΠΊΠΈ ΠΌΠ΅ΠΆΠ΄Ρƒ тях, слоТността Π½Π° заявкитС Π½Π΅ΠΏΡ€Π΅ΠΊΡŠΡΠ½Π°Ρ‚ΠΎ сС ΡƒΠ²Π΅Π»ΠΈΡ‡Π°Π²Π°, ΠΊΠΎΠ΅Ρ‚ΠΎ Π΅ особСно ΠΊΡ€ΠΈΡ‚ΠΈΡ‡Π½ΠΎ Π·Π° Π½Π°ΠΉ-Π½Π°Ρ‚ΠΎΠ²Π°Ρ€Π΅Π½ΠΈΡ‚Π΅ Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ.

Π—Π° Π΄Π° Π½Π°ΠΌΠ°Π»ΠΈΠΌ Π½Π°Ρ‚ΠΎΠ²Π°Ρ€Π²Π°Π½Π΅Ρ‚ΠΎ Π½Π° Π±Π°Π·Π°Ρ‚Π° Π΄Π°Π½Π½ΠΈ, Ρ€Π΅ΡˆΠΈΡ…ΠΌΠ΅ Π΄Π° напишСм скрипт, ΠΊΠΎΠΉΡ‚ΠΎ Π΄Π° ΠΏΡ€Π΅Ρ…Π²ΡŠΡ€Π»Ρ стари записи ΠΎΡ‚ Π½Π°ΠΉ-ΠΎΠ±Π΅ΠΌΠ½ΠΈΡ‚Π΅ ΠΈ Π½Π°Ρ‚ΠΎΠ²Π°Ρ€Π΅Π½ΠΈ Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ Π² Π°Ρ€Ρ…ΠΈΠ²ΠΈΡ€Π°Π½ΠΈ (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ ΠΎΡ‚ task Π² task_archive).

Π’Π°Π·ΠΈ Π·Π°Π΄Π°Ρ‡Π° сС услоТнява ΠΎΡ‚ голСмия Π±Ρ€ΠΎΠΉ Π²Ρ€ΡŠΠ·ΠΊΠΈ ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ‚Π°Π±Π»ΠΈΡ†ΠΈΡ‚Π΅: просто прСмСстСтС Ρ€Π΅Π΄ΠΎΠ²Π΅ ΠΎΡ‚ task Π² task_archive Π½Π΅ Π΅ Π΄ΠΎΡΡ‚Π°Ρ‚ΡŠΡ‡Π½ΠΎ, ΠΏΡ€Π΅Π΄ΠΈ Ρ‚ΠΎΠ²Π° трябва Π΄Π° Π½Π°ΠΏΡ€Π°Π²ΠΈΡ‚Π΅ ΡΡŠΡ‰ΠΎΡ‚ΠΎ рСкурсивно с всички Ρ‚Π΅Π·ΠΈ ΠΏΡ€Π΅ΠΏΡ€Π°Ρ‚ΠΊΠΈ task маси.

Π©Π΅ дСмонстрирам с ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Π΄Π΅ΠΌΠΎ Π±Π°Π·Π° Π΄Π°Π½Π½ΠΈ ΠΎΡ‚ сайта postgrespro.ru:

PgGraph Π΅ ΠΏΠΎΠΌΠΎΡ‰Π½Π° ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠ° Π·Π° Π°Ρ€Ρ…ΠΈΠ²ΠΈΡ€Π°Π½Π΅ ΠΈ Π½Π°ΠΌΠΈΡ€Π°Π½Π΅ Π½Π° зависимости Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ Π² PostgreSQL
Π”Π° ΠΊΠ°ΠΆΠ΅ΠΌ, Ρ‡Π΅ трябва Π΄Π° ΠΈΠ·Ρ‚Ρ€ΠΈΠ΅ΠΌ записи ΠΎΡ‚ Ρ‚Π°Π±Π»ΠΈΡ†Π° Flights. Postgres няма Π΄Π° Π½ΠΈ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈ Π΄Π° Π½Π°ΠΏΡ€Π°Π²ΠΈΠΌ Ρ‚ΠΎΠ²Π° просто Ρ‚Π°ΠΊΠ°: ΠΏΡŠΡ€Π²ΠΎ трябва Π΄Π° ΠΈΠ·Ρ‚Ρ€ΠΈΠ΅ΠΌ записи ΠΎΡ‚ всички Ρ€Π΅Ρ„Π΅Ρ€Π΅Π½Ρ‚Π½ΠΈ Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ ΠΈ Ρ‚Π°ΠΊΠ° Π½Π°Ρ‚Π°Ρ‚ΡŠΠΊ рСкурсивно Π΄ΠΎ Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ, ΠΊΠΎΠΈΡ‚ΠΎ Π½Π΅ са Ρ€Π΅Ρ„Π΅Ρ€ΠΈΡ€Π°Π½ΠΈ ΠΎΡ‚ Π½ΠΈΠΊΠΎΠ³ΠΎ.

Π’ нашия ΠΏΡ€ΠΈΠΌΠ΅Ρ€ ΠΏΡ€ΠΈ Flights сС отнася Ticket_flights, Π° Π²ΡŠΡ€Ρ…Ρƒ нСя - Boarding_passes.

Π‘Π»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»Π½ΠΎ трябва Π΄Π° Π³ΠΎ ΠΈΠ·Ρ‚Ρ€ΠΈΠ΅Ρ‚Π΅ Π² слСдния Ρ€Π΅Π΄:

  1. ΠŸΠΎΠ»ΡƒΡ‡Π°Π²Π°ΠΌΠ΅ стойноститС Π½Π° ΠΏΡŠΡ€Π²ΠΈΡ‡Π½ΠΈΡ‚Π΅ ΠΊΠ»ΡŽΡ‡ΠΎΠ²Π΅ (PK) Π½Π° Ρ€Π΅Π΄ΠΎΠ²Π΅Ρ‚Π΅ Π² Ticket_flights, ΠΊΠΎΠΈΡ‚ΠΎ сС отнасят Π΄ΠΎ Ρ€Π΅Π΄ΠΎΠ²Π΅Ρ‚Π΅, Π² ΠΊΠΎΠΈΡ‚ΠΎ трябва Π΄Π° сС изтрият Flights.
  2. ΠŸΠΎΠ»ΡƒΡ‡Π°Π²Π°ΠΌΠ΅ PK Ρ€Π΅Π΄ΠΎΠ²Π΅ Boarding_passes, ΠΊΠΎΠΈΡ‚ΠΎ сС отнасят Π΄ΠΎ Ticket_flights.
  3. Π˜Π·Ρ‚Ρ€ΠΈΠ²Π°ΠΌΠ΅ Ρ€Π΅Π΄ΠΎΠ²Π΅ ΠΏΠΎ PK ΠΎΡ‚ ΡΡ‚ΡŠΠΏΠΊΠ° 2 Π² Ρ‚Π°Π±Π»ΠΈΡ†Π°Ρ‚Π° Boarding_passes.
  4. Π˜Π·Ρ‚Ρ€ΠΈΠΉΡ‚Π΅ Ρ€Π΅Π΄ΠΎΠ²Π΅Ρ‚Π΅ ΠΏΠΎ PK ΠΎΡ‚ ΡΡ‚ΡŠΠΏΠΊΠ° 1 Π² Ticket_flights.
  5. ΠŸΡ€Π΅ΠΌΠ°Ρ…Π²Π°Π½Π΅ Π½Π° Π»ΠΈΠ½ΠΈΠΈ ΠΎΡ‚ Flights.

Π Π΅Π·ΡƒΠ»Ρ‚Π°Ρ‚ΡŠΡ‚ бСшС ΠΏΠΎΠΌΠΎΡ‰Π½Π° ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠ°, Π½Π°Ρ€Π΅Ρ‡Π΅Π½Π° PgGraph, която Ρ€Π΅ΡˆΠΈΡ…ΠΌΠ΅ Π΄Π° Π½Π°ΠΏΡ€Π°Π²ΠΈΠΌ с ΠΎΡ‚Π²ΠΎΡ€Π΅Π½ ΠΊΠΎΠ΄.

Как Π΄Π° ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π°Ρ‚Π΅

ΠŸΠΎΠΌΠΎΡ‰Π½Π°Ρ‚Π° ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠ° ΠΏΠΎΠ΄Π΄ΡŠΡ€ΠΆΠ° Π΄Π²Π° Ρ€Π΅ΠΆΠΈΠΌΠ° Π½Π° ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π°Π½Π΅:

  • ОбаТданС ΠΎΡ‚ командния Ρ€Π΅Π΄ (pggraph …).
  • ИзползванС Π² ΠΊΠΎΠ΄ Π½Π° Python (клас PgGraphApi).

Π˜Π½ΡΡ‚Π°Π»ΠΈΡ€Π°Π½Π΅ ΠΈ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€ΠΈΡ€Π°Π½Π΅

ΠŸΡŠΡ€Π²ΠΎ трябва Π΄Π° инсталиратС ΠΏΠΎΠΌΠΎΡ‰Π½Π°Ρ‚Π° ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠ° ΠΎΡ‚ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅Ρ‚ΠΎ Π½Π° Pypi:

pip3 install pggraph

Π‘Π»Π΅Π΄ Ρ‚ΠΎΠ²Π° ΡΡŠΠ·Π΄Π°ΠΉΡ‚Π΅ Ρ„Π°ΠΉΠ» config.ini Π½Π° Π»ΠΎΠΊΠ°Π»Π½Π°Ρ‚Π° машина с конфигурацията Π½Π° Π±Π°Π·Π°Ρ‚Π° Π΄Π°Π½Π½ΠΈ ΠΈ скрипта Π·Π° Π°Ρ€Ρ…ΠΈΠ²ΠΈΡ€Π°Π½Π΅:

[db]
host = localhost
port = 5432
user = postgres
password = postgres
dbname = postgres
schema = public ; ΠΠ΅ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€, ΡƒΠΊΠ°Π·Π°Π½ΠΎ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ

[archive]  ; Π”Π°Π½Π½Ρ‹ΠΉ Ρ€Π°Π·Π΄Π΅Π» Π·Π°ΠΏΠΎΠ»Π½ΡΡ‚ΡŒ Π½Π΅ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ, Π½ΠΈΠΆΠ΅ ΡƒΠΊΠ°Π·Π°Π½Ρ‹ значСния ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ
is_debug = false
chunk_size = 1000
max_depth = 20
to_archive = true
archive_suffix = 'archive'

Π‘Ρ‚Π°Ρ€Ρ‚ΠΈΡ€Π°ΠΉΡ‚Π΅ ΠΎΡ‚ ΠΊΠΎΠ½Π·ΠΎΠ»Π°Ρ‚Π°

ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΈ

$ pggraph -h
usage: pggraph action [-h] --table TABLE [--ids IDS] [--config_path CONFIG_PATH]
positional arguments:
  action        required action: archive_table, get_table_references, get_rows_references

optional arguments:
  -h, --help                    show this help message and exit
  --table TABLE                 table name
  --ids IDS                     primary key ids, separated by comma, e.g. 1,2,3
  --config_path CONFIG_PATH     path to config.ini
  --log_path LOG_PATH           path to log dir
  --log_level LOG_LEVEL         log level (debug, info, error)

ΠŸΠΎΠ·ΠΈΡ†ΠΈΠΎΠ½Π½ΠΈ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ΠΈ:

  • action - Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ дСйствиС: archive_table, get_table_references ΠΈΠ»ΠΈ get_rows_references.

Π˜ΠΌΠ΅Π½ΡƒΠ²Π°Π½ΠΈ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ΠΈ:

  • --config_path β€” ΠΏΡŠΡ‚ Π΄ΠΎ конфигурационния Ρ„Π°ΠΉΠ»;
  • --table β€” Ρ‚Π°Π±Π»ΠΈΡ†Π°, с която трябва Π΄Π° ΠΈΠ·Π²ΡŠΡ€ΡˆΠΈΡ‚Π΅ дСйствиС;
  • --ids β€” списък с ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ΠΈ, Ρ€Π°Π·Π΄Π΅Π»Π΅Π½ΠΈ със Π·Π°ΠΏΠ΅Ρ‚Π°ΠΈ, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, 1,2,3 (Π½Π΅Π·Π°Π΄ΡŠΠ»ΠΆΠΈΡ‚Π΅Π»Π΅Π½ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚ΡŠΡ€);
  • --log_path β€” ΠΏΡŠΡ‚ Π΄ΠΎ ΠΏΠ°ΠΏΠΊΠ°Ρ‚Π° Π·Π° рСгистрационни Ρ„Π°ΠΉΠ»ΠΎΠ²Π΅ (Π½Π΅Π·Π°Π΄ΡŠΠ»ΠΆΠΈΡ‚Π΅Π»Π΅Π½ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚ΡŠΡ€, ΠΏΠΎ ΠΏΠΎΠ΄Ρ€Π°Π·Π±ΠΈΡ€Π°Π½Π΅ β€” домашна ΠΏΠ°ΠΏΠΊΠ°);
  • --log_level β€” Π½ΠΈΠ²ΠΎ Π½Π° рСгистриранС (Π½Π΅Π·Π°Π΄ΡŠΠ»ΠΆΠΈΡ‚Π΅Π»Π΅Π½ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚ΡŠΡ€, ΠΏΠΎ ΠΏΠΎΠ΄Ρ€Π°Π·Π±ΠΈΡ€Π°Π½Π΅ Π΅ INFO).

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ΠΈ Π·Π° ΠΊΠΎΠΌΠ°Π½Π΄ΠΈ

АрхивиранС Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†Π°

ΠžΡΠ½ΠΎΠ²Π½Π°Ρ‚Π° функция Π½Π° ΠΏΠΎΠΌΠΎΡ‰Π½Π°Ρ‚Π° ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠ° Π΅ Π°Ρ€Ρ…ΠΈΠ²ΠΈΡ€Π°Π½Π΅Ρ‚ΠΎ Π½Π° Π΄Π°Π½Π½ΠΈ, Ρ‚.Π΅. ΠΏΡ€Π΅Ρ…Π²ΡŠΡ€Π»ΡΠ½Π΅ Π½Π° Ρ€Π΅Π΄ΠΎΠ²Π΅ ΠΎΡ‚ основната Ρ‚Π°Π±Π»ΠΈΡ†Π° към Π°Ρ€Ρ…ΠΈΠ²Π½Π°Ρ‚Π° Ρ‚Π°Π±Π»ΠΈΡ†Π° (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ ΠΎΡ‚ Ρ‚Π°Π±Π»ΠΈΡ†Π°Ρ‚Π° ΠΊΠ½ΠΈΠ³ΠΈ Π² ΠΊΠ½ΠΈΠ³ΠΈ_Π°Ρ€Ρ…ΠΈΠ²).

ΠŸΠΎΠ΄Π΄ΡŠΡ€ΠΆΠ° сС ΠΈ ΠΈΠ·Ρ‚Ρ€ΠΈΠ²Π°Π½Π΅ Π±Π΅Π· Π°Ρ€Ρ…ΠΈΠ²ΠΈΡ€Π°Π½Π΅: Π·Π° Ρ‚ΠΎΠ²Π° трябва Π΄Π° Π·Π°Π΄Π°Π΄Π΅Ρ‚Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚ΡŠΡ€Π° Π² config.ini to_archive = нСвярно).

НСобходими ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΈ - config_path, Ρ‚Π°Π±Π»ΠΈΡ†Π° ΠΈ ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ΠΈ.

Π‘Π»Π΅Π΄ стартиранС записитС Ρ‰Π΅ Π±ΡŠΠ΄Π°Ρ‚ рСкурсивно ΠΈΠ·Ρ‚Ρ€ΠΈΡ‚ΠΈ ids Π² Ρ‚Π°Π±Π»ΠΈΡ†Π°Ρ‚Π° table ΠΈ във всички Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ, ΠΊΠΎΠΈΡ‚ΠΎ сС отнасят Π΄ΠΎ Π½Π΅Π³ΠΎ.

$ pggraph archive_table --config_path config.hw.local.ini --table flights --ids 1,2,3
2020-06-20 19:27:44 INFO: flights - START
2020-06-20 19:27:44 INFO: flights - start archive_recursive 3 rows (depth=0)
2020-06-20 19:27:44 INFO:       START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:       ticket_flights - start archive_recursive 3 rows (depth=1)
2020-06-20 19:27:44 INFO:               START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:               boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO:                       START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:                       END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:               boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO:               boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO:                       START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:                       END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:               boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO:               boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO:                       START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:                       END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:               boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO:               boarding_passes - start archive_recursive 3 rows (depth=2)
2020-06-20 19:27:44 INFO:                       START ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:                       END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:               boarding_passes - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO:               END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO:       ticket_flights - archive_by_ids 3 rows by ticket_no, flight_id
2020-06-20 19:27:44 INFO:       END ARCHIVE REFERRING TABLES
2020-06-20 19:27:44 INFO: flights - archive_by_ids 3 rows by id
2020-06-20 19:27:44 INFO: flights - END

НамСрСтС зависимости Π·Π° ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†Π°

Ѐункция Π·Π° Π½Π°ΠΌΠΈΡ€Π°Π½Π΅ Π½Π° зависимости Π½Π° ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†Π° table. НСобходими ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΈ - config_path ΠΈ table.

Π‘Π»Π΅Π΄ стартиранС Π½Π° Π΅ΠΊΡ€Π°Π½Π° Ρ‰Π΅ сС ΠΏΠΎΠΊΠ°ΠΆΠ΅ Ρ€Π΅Ρ‡Π½ΠΈΠΊ, ΠΊΡŠΠ΄Π΅Ρ‚ΠΎ:

  • in_refs β€” Ρ€Π΅Ρ‡Π½ΠΈΠΊ Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ, ΠΏΡ€Π΅ΠΏΡ€Π°Ρ‰Π°Ρ‰ΠΈ към Π΄Π°Π΄Π΅Π½Π°, ΠΊΡŠΠ΄Π΅Ρ‚ΠΎ ΠΊΠ»ΡŽΡ‡ΡŠΡ‚ Π΅ ΠΈΠΌΠ΅Ρ‚ΠΎ Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†Π°Ρ‚Π°, стойността Π΅ списък с ΠΎΠ±Π΅ΠΊΡ‚ΠΈ Π½Π° външСн ΠΊΠ»ΡŽΡ‡ (pk_main - ΠΏΡŠΡ€Π²ΠΈΡ‡Π΅Π½ ΠΊΠ»ΡŽΡ‡ Π² Π³Π»Π°Π²Π½Π°Ρ‚Π° Ρ‚Π°Π±Π»ΠΈΡ†Π°, pk_ref - ΠΏΡŠΡ€Π²ΠΈΡ‡Π΅Π½ ΠΊΠ»ΡŽΡ‡ Π² Ρ€Π΅Ρ„Π΅Ρ€Π΅Π½Ρ‚Π½Π°Ρ‚Π° Ρ‚Π°Π±Π»ΠΈΡ†Π°, fk_ref β€” ΠΈΠΌΠ΅Ρ‚ΠΎ Π½Π° ΠΊΠΎΠ»ΠΎΠ½Π°Ρ‚Π°, която Π΅ външСн ΠΊΠ»ΡŽΡ‡ към ΠΈΠ·Ρ…ΠΎΠ΄Π½Π°Ρ‚Π° Ρ‚Π°Π±Π»ΠΈΡ†Π°);
  • out_refs β€” Ρ€Π΅Ρ‡Π½ΠΈΠΊ Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†ΠΈΡ‚Π΅, към ΠΊΠΎΠΉΡ‚ΠΎ сС отнася.

$ pggraph get_table_references --config_path config.hw.local.ini --table flights
{'in_refs': {'ticket_flights': [ForeignKey(pk_main='flight_id', pk_ref='ticket_no, flight_id', fk_ref='flight_id')]},
 'out_refs': {'aircrafts': [ForeignKey(pk_main='aircraft_code', pk_ref='flight_id', fk_ref='aircraft_code')],
              'airports': [ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='arrival_airport'),
                           ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='departure_airport')]}}

НамиранС Π½Π° ΠΏΡ€Π΅ΠΏΡ€Π°Ρ‚ΠΊΠΈ към Π½ΠΈΠ·ΠΎΠ²Π΅ с посочСния ΠΏΡŠΡ€Π²ΠΈΡ‡Π΅Π½ ΠΊΠ»ΡŽΡ‡

Ѐункция Π·Π° Ρ‚ΡŠΡ€ΡΠ΅Π½Π΅ Π½Π° Ρ€Π΅Π΄ΠΎΠ²Π΅ Π² Π΄Ρ€ΡƒΠ³ΠΈ Ρ‚Π°Π±Π»ΠΈΡ†ΠΈ, ΠΊΠΎΠΈΡ‚ΠΎ ΠΏΡ€Π΅ΠΏΡ€Π°Ρ‰Π°Ρ‚ към Ρ€Π΅Π΄ΠΎΠ²Π΅ Ρ‡Ρ€Π΅Π· външСн ΠΊΠ»ΡŽΡ‡ ids маси table. НСобходими ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΈ - config_path, table ΠΈ ids.

Π‘Π»Π΅Π΄ стартиранС Π½Π° Π΅ΠΊΡ€Π°Π½Π° Ρ‰Π΅ сС ΠΏΠΎΠΊΠ°ΠΆΠ΅ Ρ€Π΅Ρ‡Π½ΠΈΠΊ със слСдната структура:

{
	pk_id_1: {
		reffering_table_name_1: {
			foreign_key_1: [
				{row_pk_1: value, row_pk_2: value},
				...
			], 
			...
		},
		...
	},
	pk_id_2: {...},
	...
}

ΠŸΡ€ΠΈΠΌΠ΅Ρ€Π½ΠΎ ΠΎΠ±Π°ΠΆΠ΄Π°Π½Π΅:

$ pggraph get_rows_references --config_path config.hw.local.ini --table flights --ids 1,2,3
{1: {'ticket_flights': {'flight_id': [{'flight_id': 1,
                                       'ticket_no': '0005432816945'},
                                      {'flight_id': 1,
                                       'ticket_no': '0005432816941'}]}},
 2: {'ticket_flights': {'flight_id': [{'flight_id': 2,
                                       'ticket_no': '0005433101832'},
                                      {'flight_id': 2,
                                       'ticket_no': '0005433101864'},
                                      {'flight_id': 2,
                                       'ticket_no': '0005432919715'}]}},
 3: {'ticket_flights': {'flight_id': [{'flight_id': 3,
                                       'ticket_no': '0005432817560'},
                                      {'flight_id': 3,
                                       'ticket_no': '0005432817568'},
                                      {'flight_id': 3,
                                       'ticket_no': '0005432817559'}]}}}

Π£ΠΏΠΎΡ‚Ρ€Π΅Π±Π° Π² ΠΊΠΎΠ΄Π°

Π’ допълнСниС към стартиранСто ΠΉ Π² ΠΊΠΎΠ½Π·ΠΎΠ»Π°Ρ‚Π°, Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ°Ρ‚Π° ΠΌΠΎΠΆΠ΅ Π΄Π° сС ΠΈΠ·ΠΏΠΎΠ»Π·Π²Π° Π² ΠΊΠΎΠ΄ Π½Π° Python. ΠŸΡ€ΠΈΠΌΠ΅Ρ€ΠΈ Π·Π° повиквания Π² ΠΈΠ½Ρ‚Π΅Ρ€Π°ΠΊΡ‚ΠΈΠ²Π½Π°Ρ‚Π° срСда Π½Π° iPython са ΠΏΠΎΠΊΠ°Π·Π°Π½ΠΈ ΠΏΠΎ-Π΄ΠΎΠ»Ρƒ.

АрхивиранС Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†Π°

>>> from pg_graph.main import setup_logging
>>> setup_logging(log_level='DEBUG')
>>> from pg_graph.api import PgGraphApi
>>> api = PgGraphApi('config.hw.local.ini')
>>> api.archive_table('flights', [4,5])
2020-06-20 23:12:08 INFO: flights - START
2020-06-20 23:12:08 INFO: flights - start archive_recursive 2 rows (depth=0)
2020-06-20 23:12:08 INFO: 	START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: 	ticket_flights - ForeignKey(pk_main='flight_id', pk_ref='flight_id, ticket_no', fk_ref='flight_id')
2020-06-20 23:12:08 DEBUG: 	SQL('SELECT flight_id, ticket_no FROM bookings.ticket_flights WHERE (flight_id) IN (%s, %s)')
2020-06-20 23:12:08 INFO: 	ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: 		START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: 		boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: 		boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: 		SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: 		DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: 		END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: 	ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: 	SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: 	DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: 	INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: 	ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: 		START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: 		boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: 		boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: 		SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: 		DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: 		END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: 	ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: 	SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: 	DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: 	INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: 	ticket_flights - start archive_recursive 30 rows (depth=1)
2020-06-20 23:12:08 INFO: 		START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: 		boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: 		boarding_passes - archive_by_fk 30 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: 		SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: 		DELETE FROM boarding_passes by FK flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 INFO: 		END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: 	ticket_flights - archive_by_ids 30 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: 	SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: 	DELETE FROM ticket_flights by flight_id, ticket_no - 30 rows
2020-06-20 23:12:08 DEBUG: 	INSERT INTO ticket_flights_archive - 30 rows
2020-06-20 23:12:08 INFO: 	ticket_flights - start archive_recursive 3 rows (depth=1)
2020-06-20 23:12:08 INFO: 		START ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 DEBUG: 		boarding_passes - ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 INFO: 		boarding_passes - archive_by_fk 3 rows by ForeignKey(pk_main='flight_id, ticket_no', pk_ref='flight_id, ticket_no', fk_ref='flight_id, ticket_no')
2020-06-20 23:12:08 DEBUG: 		SQL('CREATE TABLE IF NOT EXISTS bookings.boarding_passes_archive (LIKE bookings.boarding_passes)')
2020-06-20 23:12:08 DEBUG: 		DELETE FROM boarding_passes by FK flight_id, ticket_no - 3 rows
2020-06-20 23:12:08 INFO: 		END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: 	ticket_flights - archive_by_ids 3 rows by flight_id, ticket_no
2020-06-20 23:12:08 DEBUG: 	SQL('CREATE TABLE IF NOT EXISTS bookings.ticket_flights_archive (LIKE bookings.ticket_flights)')
2020-06-20 23:12:08 DEBUG: 	DELETE FROM ticket_flights by flight_id, ticket_no - 3 rows
2020-06-20 23:12:08 DEBUG: 	INSERT INTO ticket_flights_archive - 3 rows
2020-06-20 23:12:08 INFO: 	END ARCHIVE REFERRING TABLES
2020-06-20 23:12:08 INFO: flights - archive_by_ids 2 rows by flight_id
2020-06-20 23:12:09 DEBUG: SQL('CREATE TABLE IF NOT EXISTS bookings.flights_archive (LIKE bookings.flights)')
2020-06-20 23:12:09 DEBUG: DELETE FROM flights by flight_id - 2 rows
2020-06-20 23:12:09 DEBUG: INSERT INTO flights_archive - 2 rows
2020-06-20 23:12:09 INFO: flights - END

НамСрСтС зависимости Π·Π° ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π° Ρ‚Π°Π±Π»ΠΈΡ†Π°

>>> from pg_graph.api import PgGraphApi
>>> from pprint import pprint
>>> api = PgGraphApi('config.hw.local.ini')
>>> res = api.get_table_references('flights')
>>> pprint(res)
{'in_refs': {'ticket_flights': [ForeignKey(pk_main='flight_id', pk_ref='flight_id, ticket_no', fk_ref='flight_id')]},
 'out_refs': {'aircrafts': [ForeignKey(pk_main='aircraft_code', pk_ref='flight_id', fk_ref='aircraft_code')],
              'airports': [ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='arrival_airport'),
                           ForeignKey(pk_main='airport_code', pk_ref='flight_id', fk_ref='departure_airport')]}}

НамиранС Π½Π° ΠΏΡ€Π΅ΠΏΡ€Π°Ρ‚ΠΊΠΈ към Π½ΠΈΠ·ΠΎΠ²Π΅ с посочСния ΠΏΡŠΡ€Π²ΠΈΡ‡Π΅Π½ ΠΊΠ»ΡŽΡ‡

>>> from pg_graph.api import PgGraphApi
>>> from pprint import pprint
>>> api = PgGraphApi('config.hw.local.ini')
>>> rows = api.get_rows_references('flights', [1,2,3])
>>> pprint(rows)
{1: {'ticket_flights': {'flight_id': [{'flight_id': 1,
                                       'ticket_no': '0005432816945'},
                                      {'flight_id': 1,
                                       'ticket_no': '0005432816941'}]}},
 2: {'ticket_flights': {'flight_id': [{'flight_id': 2,
                                       'ticket_no': '0005433101832'},
                                      {'flight_id': 2,
                                       'ticket_no': '0005433101864'},
                                      {'flight_id': 2,
                                       'ticket_no': '0005432919715'}]}},
 3: {'ticket_flights': {'flight_id': [{'flight_id': 3,
                                       'ticket_no': '0005432817560'},
                                      {'flight_id': 3,
                                       'ticket_no': '0005432817568'},
                                      {'flight_id': 3,
                                       'ticket_no': '0005432817559'}]}}}

Π˜Π·Ρ…ΠΎΠ΄Π½ΠΈΡΡ‚ ΠΊΠΎΠ΄ Π½Π° Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ°Ρ‚Π° Π΅ Π΄ΠΎΡΡ‚ΡŠΠΏΠ΅Π½ Π½Π° GitHub ΠΏΠΎΠ΄ Π»ΠΈΡ†Π΅Π½Π· Π½Π° MIT, ΠΊΠ°ΠΊΡ‚ΠΎ ΠΈ Π² Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅Ρ‚ΠΎ PyPI.

Π©Π΅ сС Ρ€Π°Π΄Π²Π°ΠΌ Π½Π° ΠΊΠΎΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈ, Π°Π½Π³Π°ΠΆΠΈΠΌΠ΅Π½Ρ‚ΠΈ ΠΈ прСдлоТСния.

Π©Π΅ сС ΠΎΠΏΠΈΡ‚Π°ΠΌ Π΄Π° отговоря Π½Π° Π²ΡŠΠΏΡ€ΠΎΡΠΈ, Π΄ΠΎΠΊΠΎΠ»ΠΊΠΎΡ‚ΠΎ ΠΌΠΎΠ³Π°, Ρ‚ΡƒΠΊ ΠΈ Π² Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅Ρ‚ΠΎ.

Π˜Π·Ρ‚ΠΎΡ‡Π½ΠΈΠΊ: www.habr.com

ДобавянС Π½Π° Π½ΠΎΠ² ΠΊΠΎΠΌΠ΅Π½Ρ‚Π°Ρ€