Sejarah mengetik pada contoh satu proyek besar

Halo semuanya! Hari ini saya akan menceritakan kisah pengembangan mengetik pada salah satu proyek di Ostrovok.ru .



Kisah ini dimulai jauh sebelum sensasi mengetik di python3.5 , apalagi, itu dimulai di dalam proyek yang ditulis dalam python2.7 .

2013 : baru-baru ini ada rilis python3.3 , tidak ada gunanya bermigrasi ke versi baru, karena tidak menambahkan fitur spesifik, dan akan ada banyak rasa sakit dan penderitaan selama masa transisi.

Saya terlibat dalam proyek Mitra di Ostrovok.ru - layanan ini bertanggung jawab untuk segala sesuatu yang berkaitan dengan integrasi, reservasi, statistik, dan akun pribadi. Kami menggunakan kedua API internal untuk layanan microser lainnya dari perusahaan, dan API eksternal untuk mitra kami.

Pada titik tertentu, tim membentuk pendekatan berikut untuk menulis penangan HTTP atau semacam logika bisnis:

1) input dan output data harus dijelaskan oleh suatu struktur (kelas),
2) isi instance dari struktur harus divalidasi sesuai dengan deskripsi,
3) fungsi yang mengambil struktur pada input dan memberikan struktur pada output harus memeriksa tipe data pada input dan output, masing-masing.

Saya tidak akan membahas setiap poin secara rinci, contoh di bawah ini harus cukup untuk memahami apa yang dipertaruhkan.

Contoh
.
import datetime as dt from contracts import new_contract, contract from schematics.models import Model from schematics.types import IntType, DateType # in class OrderInfoData(Model): order_id = IntType(required=True) # out class OrderInfoResult(Model): order_id = IntType(required=True) checkin_at = DateType(required=True) checkout_at = DateType(required=True) cancelled_at = DateType(required=False) @new_contract def pyOrderInfoData(x): return isinstance(x, OrderInfoData) @new_contract def pyOrderInfoResult(x): return isinstance(x, OrderInfoResult) @contract def get_order_info(data_in): """ :type data_in: pyOrderInfoData :rtype: pyOrderInfoResult """ return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) ) if __name__ == '__main__': data_in = OrderInfoData(dict(order_id=777)) data_out = get_order_info(data_in) print(data_out.to_native()) 


Contohnya menggunakan perpustakaan: schematics dan pycontracts .

* skema - cara untuk menggambarkan dan memvalidasi data.
* pycontracts - cara untuk memeriksa input / output dari suatu fungsi dalam runtime.

Pendekatan ini memungkinkan Anda untuk:

  • lebih mudah untuk menulis tes - masalah dengan validasi tidak muncul, dan hanya logika bisnis yang dibahas.
  • untuk menjamin format dan kualitas respons dalam API - kerangka kerja yang kaku muncul untuk apa yang siap kami terima dan apa yang dapat kami berikan.
  • lebih mudah untuk memahami / memperbaiki format respons jika itu adalah struktur yang kompleks dengan tingkat bersarang yang berbeda.

Penting untuk memahami bahwa pengecekan tipe (non-validasi) hanya berfungsi dalam runtime , dan ini nyaman untuk pengembangan lokal, menjalankan tes dalam CI dan memverifikasi bahwa rilis kandidat bekerja dalam lingkungan pementasan . Dalam lingkungan produksi, ini harus dinonaktifkan, jika tidak server akan melambat.

Tahun-tahun berlalu, proyek kami tumbuh, logika bisnis yang lebih baru dan kompleks muncul, jumlah API yang menangani setidaknya tidak berkurang.

Pada titik tertentu, saya mulai memperhatikan bahwa peluncuran proyek sudah berlangsung beberapa detik - ini menjengkelkan, karena setiap kali saya mengedit kode dan menjalankan tes saya harus duduk dan menunggu untuk waktu yang lama. Ketika menunggu ini mulai memakan waktu 8-10 detik, kami akhirnya memutuskan untuk mencari tahu apa yang sedang terjadi di bawah tenda.

Faktanya, semuanya ternyata cukup sederhana. Ketika memulai sebuah proyek, pycontracts library mem-parsing semua docstring yang dicakup oleh @contract untuk mendaftarkan semua struktur dalam memori dan kemudian memeriksanya dengan benar. Ketika jumlah struktur dalam suatu proyek berjumlah ribuan, semua ini mulai melambat.

Apa yang harus dilakukan? Jawaban yang benar adalah mencari solusi lain, untungnya di halaman tersebut sudah 2018 ( python3.5 - python3.6 ), dan kami sudah memindahkan proyek kami ke python3.6 .

Saya mulai mempelajari solusi alternatif dan berpikir tentang bagaimana memigrasikan suatu proyek dari "deskripsi tipe pycontracts + dalam docstring " ke "deskripsi tipe sesuatu + dalam mengetik anotasi ". Ternyata jika Anda meningkatkan pycontracts ke versi terbaru, Anda dapat mendeskripsikan tipe dalam mengetik gaya anotasi , misalnya, mungkin terlihat seperti ini:

 @contract def get_order_info(data_in: OrderInfoData) -> OrderInfoResult: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) ) 

Masalah dimulai jika Anda perlu menggunakan struktur dari pengetikan , misalnya Opsional atau Gabungan , karena pycontracts TIDAK tahu cara bekerja dengannya:

 from typing import Optional @contract def get_order_info(data_in: OrderInfoData) -> Optional[OrderInfoResult]: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) ) 

Saya mulai mencari pustaka alternatif untuk jenis pengecekan runtime :

* menegakkan
* ketikan
* tipe-tipe

Menegakkan pada waktu itu tidak mendukung python3.7 , tetapi kami telah memperbarui, pytypes tidak suka sintaksis, sebagai hasilnya, pilihan jatuh pada typeguard .

 from typeguard import typechecked @typechecked def get_order_info(data_in: OrderInfoData) -> Optional[OrderInfoResult]: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) ) 

Berikut adalah contoh dari proyek nyata:

 @typechecked def view( request: HttpRequest, data_in: AffDeeplinkSerpIn, profile: Profile, contract: Contract, ) -> AffDeeplinkSerpOut: ... @typechecked def create_contract( user: Union[User, AnonymousUser], user_uid: Optional[str], params: RegistrationCreateSchemaIn, account_manager: Manager, support_manager: Manager, sales_manager: Optional[Manager], legal_entity: LegalEntity, partner: Partner, ) -> tuple: ... @typechecked def get_metaorder_ids_from_ordergroup_orders( orders: Tuple[OrderGroupOrdersIn, ...], contract: Contract ) -> list: ... 

Hasilnya, setelah proses refactoring yang panjang, kami dapat sepenuhnya mentransfer proyek ke mengetik + anotasi pengetikan .

Hasil apa yang telah kami capai:

  • Proyek dimulai dalam 2-3 detik, yang setidaknya tidak mengganggu.
  • pembacaan kode telah meningkat.
  • proyek ini menjadi lebih kecil baik dalam jumlah baris maupun dalam file, karena tidak ada lagi pendaftaran struktur melalui @new_contract .
  • IDE PyCharm yang cerdas telah menjadi lebih baik dalam mengindeks proyek dan membuat petunjuk yang berbeda, karena sekarang ini bukan komentar, tetapi impor jujur.
  • Anda dapat menggunakan analisis statis seperti mypy dan pyre -check , karena mereka mendukung kerja dengan mengetik anotasi .
  • Komunitas python secara keseluruhan bergerak menuju mengetik dalam satu bentuk atau yang lain, yaitu, tindakan saat ini adalah investasi di masa depan proyek.
  • kadang-kadang ada masalah dengan impor siklik, tetapi ada beberapa dari mereka, dan mereka dapat diabaikan.

Semoga artikel ini bermanfaat bagi Anda!

Referensi:
* menegakkan
* ketikan
* tipe-tipe
* kontrak
* skema

Source: https://habr.com/ru/post/id443470/


All Articles