Mencoba Kembali Python Typehint
Seperti yang saya tulis di tulisan saya sebelumnya, saat ini saya lebih banyak menulis kode dengan Go dan akhirnya berhasil mendapatkan feel menulis dengan type. Karena hal itu, akhirnya saya ingin mencoba kembali hal yang dulu sempat dicoba tapi berhenti karena menyebalkan — hal itu adalah Python dengan typehint.
Typehint dan Ekspektasi
Sebelum melangkah lebih jauh, saya memang harus mengatur ekspektasi, karena bagaimanapun secara natural Python bukanlah static type, dan typehint hanya sebagai petunjuk atau paling mentok ya himbauan.
Karena tidak ada paksaan dalam penggunaan type di Python, maka jika kita menulis kode seperti ini:
def must_int(param: int) -> int:
return param
Walaupun kita memberi informasi bahwa yang param adalah int
dan response berupa int
, kita tetap bisa melakukan seperti ini dan kode tetap berjalan:
name = "hallo"
res = must_int(name)
Setelah melihat di atas, mungkin muncul pertanyaan: lalu, untuk apa typehint?
Dari yang kemarin saya coba, typehint membantu dalam:
- Kejelasan
- Dukungan IDE
Dengan typehint, kita bisa melihat tanpa meraba-raba param yang perlu digunakan itu type datanya apa, tanpa harus melihat detail kode. Dan dukungan IDE untuk autocompletion pun jadi lebih enak.

Lalu bagaimana kalau kita sudah menggunakan typehint dan ingin melakukan pengecekan agar tidak sebatas tentang kejelasan kode dan dukungan IDE? Jawabannya adalah pustaka type checking. Di Python, setidaknya ada 3 yang populer: MyPy, Pyright, dan BasedPyright. Dari ketiga itu, yang sudah saya coba baru dua: MyPy dan BasedPyright.
Intinya, ketiga pustaka tadi membantu untuk pengecekan kode Python kita agar digunakan semestinya sesuai dengan typehint — seperti contoh di atas, ketika dilakukan pengecekan akan muncul error.
Typehint di Legacy Code
Kalau kita memulai aplikasi baru, terutama menggunakan kode dasar dari FastAPI, ini cukup "mudah", karena sejak awal FastAPI dengan Pydantic-nya cukup mengenalkan penggunaan type di kodenya. Tapi masalah muncul kalau kita mencoba memasang typehint dan typecheck di legacy code, dan kodenya adalah Django.
Salah satu alasan saya berhenti mencoba typehint sebelumnya adalah karena saya memaksa menggunakan MyPy dengan Django, sedangkan Django banyak magic class yang dari bawaannya cukup sulit untuk ditentukan type-nya. Bahkan untuk membuat generic pun malah menambah frustasi. Tapi, pemasangan secara berkala bisa saya lakukan dengan pustaka BasedPyright dan django-types.
django-types
membantu membuat stub untuk internal Django, sehingga untuk penggunaan fungsi-fungsi internal Django bisa dibantu dengan stub ini.BasedPyright
konfigurasinya memungkinkan dipasang secara bertahap.
contoh kode yang gagal
def home_view(request: HttpRequest) -> HttpResponse:
"""Function-based view that renders a homepage with all Play objects."""
plays: QuerySet[Play] = Play.objects.all()
hello = "string"
param = must_int(hello)
# Pass the QuerySet to the template
return render(request, "home.html", {"plays": plays, "params": param})
def must_int(param: int) -> int:
return param
Saat saya menjalankan perintah basedpyright
muncul peringatan
/Users/ariesm/code/python/wip/playg/views.py
/Users/ariesm/code/python/wip/playg/views.py:12:22 - error: Argument of type "Literal['string']" cannot be assigned to parameter "param" of type "int" in function "must_int"
"Literal['string']" is not assignable to "int" (reportArgumentType)
1 error, 0 warnings, 0 notes
Mari perbaiki menjadi:
def home_view(request: HttpRequest) -> HttpResponse:
"""Function-based view that renders a homepage with all Play objects."""
plays: QuerySet[Play] = Play.objects.all()
number = 123
param = must_int(number)
# Pass the QuerySet to the template
return render(request, "home.html", {"plays": plays, "params": param})
def must_int(param: int) -> int:
return param
Saat melakukan pengecekan menjadi:
~ basedpyright
0 errors, 0 warnings, 0 notes
Berikut adalah konfigurasi basedpyright yang saya gunakan, mayoritas konfigurasi ini menggunakan rekomendasi LLM ChatGpt dan disesuaikan dengan dokumentasi dan temuan di stackoverflow
[tool.basedpyright]
# gradual, only add app "playg"
include = ["playg"]
exclude = ["**/migrations", "**/__pycache__", "**/settings.py"]
# stub coming from django stub/types
extraPaths = ["typings"] # prepare for stub
pythonVersion = "3.13"
pythonPlatform = "Linux"
typeCheckingMode = "basic"
venvPath = "."
# Enable missing imports detection for better error tracking
reportMissingImports = true
reportMissingTypeStubs = false
# Reduce noise from dynamically typed Django parts
reportUnknownMemberType = false
reportUnknownVariableType = false
reportUnknownArgumentType = false
reportUntypedClassDecorator = false
reportUntypedFunctionDecorator = false
reportUntypedBaseClass = false
reportIncompatibleVariableOverride = true
reportAttributeAccessIssue = false # Only if the stub doesn't fully fix it
[tool.django-types]
django_settings_module = "core.settings"
Untuk otomatisasi pengecekan kita bisa menggunakan pre-commit atau mungkin di pipeline CI yang dipakai.
Kesimpulan
Bagi saya, typehint di Django atau Python memang tidak bisa dipaksa sangat strict, apalagi kalau kita pakai framework seperti Django. Tapi dengan konfigurasi yang disesuaikan dengan kebutuhan, bisa sangat membantu kita dalam:
- kejelasan kode
- dukungan IDE yang optimal
- menemukan potential bug