Garry Newman powiedział żeby Unity się p..doliło

Garry Newman told Unity to "get fucked" and said the move has left him "furious."

Widać, że Big Tech zaczyna mocno deptać ludziom po odciskach. Tym razem inba jest o wprowadzenie haraczu od pojedynczej instalacji finalnego produktu. Instalacji, czyli reinstalacji też, a nie od sprzedanych kopii (co i tak byłoby odmienne od dotychczasowych opłat).

Rust dev claims Unity is "the worst company to be in charge of the Unity Engine"

So, Unity...


Źródło: https://www.gamedeveloper.com/business/rust-creator-tells-unity-to-get-fucked-as-developers-left-seething-by-new-fee

Build the API with FastAPI top of Django

Even though these two tools seem to be mutually exclusive, they are not. There is possibility to omit Django's http server and relay all HTTP traffic to FastAPI's ASGI application. These are the goals of the new package django-fastapi-bridge.

Django FastAPI Bridge automated documentation

Wait, isn't Django too old for FastAPI?

If you type "Django and fastapi" into Google you will get results that are simply misinformation. Speaking gently - they're outdated or written by persons without an experience (a real plague these days):

Can you use Django with FastAPI?

Django-ORM can also be used with FastAPI but that is simply not recommended because it will be useless to use one of the oldest framework which does not use the modern features of Python, with one of the newest framework which uses the modern features like asyncio and others to provide high performance.

The brutal truth is that it is bulls*t. Let the numbers speak for themselves. The tested endpoint looks like:

# sync version

@api.get("/permissions", response_model=PermissionListResource)
def permissions_list():
    queryset = Permission.objects.all()
    items = [{"pk": obj.pk, "name": obj.name} for obj in queryset]
    return PermissionListResource(items=items)

# async version

@api.get("/async/permissions", response_model=PermissionListResource)
async def permissions_list():
    queryset = Permission.objects.all()
    items = [{"pk": obj.pk, "name": obj.name} async for obj in queryset]
    return PermissionListResource(items=items)
  1. Django + FastAPI in sync mode: 657.16 rq/sec
  2. Django + FastAPI in fully async mode: 1568.97 rq/sec

Both served through uvicorn. So... is this a high performance Django or not? :)

Note that there also is an ApI. ("artifical pseudo intelligence") which will give you nonsense in the form of supposed knowledge. Be careful and "don't trust the science" (a "science" is a new idol these days). Ok, enough...

What for is this hybrid?

  • To have one codebase for your core (models, logic)
  • To expose Django Admin panel top of it
  • To use Django ORM, auth, permissions system and other tools with FastAPI
  • To gain from brilliant performance and automatic documentation which comes from FastAPI
  • You don't have to rely on less known or unstable solutions
  • And, if programmed in async mode in mind, gain from asynchronous features like long polling.

How it works

Django has builtin support for protections against running sync code in async context, and async emulation built on threads. So you can mix sync and async code quite safely, but with a performance loss. To gain from fully async mode you must write handlers with async in mind, which will give you the best performance on the same ASGI server.

Despite the excellent support built into Django, you should pay attention to external applications or add-ons that may be developed for synchronous mode and may lack protection and cause problems. Test your app well.

What will not work

  • Django middlewares aren't compatible because of different request object
  • Wherever request is used in the code, it will not be compatible with the request object from FastAPI
  • Context processors based on request object are mostly unusable
  • Django's URL routing will not work with FastAPI routing
  • Sessions handling (not an issue when using external OAuth2 server)

What's different

  • authenticated user object must be injected as a dependency explicitely (user=Depends(get_current_user))
  • serialization and model updates will require additional helpers (a Pydantic models coupled with Django ORM models)
  • authentication process is delegated to a separate service (OAuth2 recommended), and can be based on Django OAuth2, and share the same database (it is easier to integrate both in such case)

Alternatives? No.

  • Django Rest Framework - slow, not fully async, old and not best design (which is mentioned in the docs)
  • Django Ninja - however inspired by FastAPI, it is slow and shares part of not best design from DRF

Should I use FastAPI with Django?

"It's a Frankenstein"

— random python-like developer

You can, you don't have to. Mid-sized and big-sized projects are "Frankensteins" nowdays. Monoliths or microservices - does not matter. For such projects, where you must maintain them, it is important to have consistent and simplest stack. It is easier to support Django and FastAPI instead of tens of differrent technologies, frameworks and tools, not mentioning difficulties with hiring developers who knows A, B, C, D, E, etc frameworks or libraries. It is even a matter of team management not technology itself.

"But you broke Django!"

— maybe python-like developer

There is no practical difference between not using features A, B, C because they are not needed, and not being able to use A, B or C for technical reasons. Also, I didn't broke Django in any way. It's just a mental thing. But for sure, you should be aware of the limitations.

Example architecture concept

Example architecture based on Django FastAPI Bridge

Summing up

Django FastAPI Bridge is NOT fully compilant with Django, because it is not using Django ASGI/WSGI http server. Features based on middleware or request/response objects simply won't work. But, with a proper separations of concerns, you are able to use your app logic codebase to run different services top of it, and you have all battle tested tools needed to get the things done. Note that in most cases when building a RESTful API (or even an entire API), you won't miss the built-in Django middleware.

The really missing parts the Django FastAPI Bridge will try to deliver. I am considering adding an adapter for the request object, but it can be tricky and faulty.

Rysuj smoka

Legenda głosi, że to było moje pierwsze polecenie wydane komputerowi:

Rysuj smoka - Atari XE

Dziś, po ponad 30 latach, komputer w końcu zrealizował to polecenie.

Rysuj smoka - AI

Można umierać. Choć może nie do końca, bo sztuczna "inteligencja" daje także upust swojej sztucznej "kreatywności" ;)

Rysuj smoka - głupsze AI

Reducing a mess generated by vim-ale

In case of mess in your editor window, just add this line to your .vimrc:

let g:ale_virtualtext_cursor = 'disabled'

a mess in vim A mess in VIM (look at these red inline error messages)

The root cause of the issue

Some idiot creative guys thought that mixing warning messages with code makes some sense and increases readability, and someone smart enough implemented it (to the misfortune of mankind).

A source of the mess in vim

The other one great dev enabled the mess at default (dunno why, maybe he thinks that mess in the editor window is so awesome):

An awesome idea of enabling mess in the VIM editor at default

Krótko o jakości bibliotek Python do ElasticSearch

Do Pythona istnieją dwie podstawowe biblioteki do komunikacji z ElasticSearch: elasticsearch-py oraz elasticsearch-dsl. Są one typowymi interfejsami do komunikacji z serwerem ElasticSearch. Śledzę ich rozwój od pierwszych wersji i niestety, mimo już siódmej odsłony, autorzy powielają te same błędy projektowe, czyli de facto błędy programistyczne. Utrudnia to korzystanie z bibliotek, ponieważ tworzone są sytuacje niejasne, co może mieć negatywny wpływ na stabilność i jakość rozwiązań o nie opartych.

elasticsearch-py

Pierwsza uwaga dotyczy biblioteki elasticsearch-py, czyli stosunkowo niskopoziomowego interfejsu programistycznego. Już pierwszy przykład z dokumentacji uwidacznia problem:

es = ElasticSearch()
es.indices.create(index='my-index', ignore=400)

Co robi powyższe wywołanie? Tworzy indeks my-index, a w przypadku gdy takowy już istnieje, ma za zadanie zignorować błąd, pozwalając aby program wykonał się dalej bezbłędnie.

Problem tego podejścia jest w sposobie obsługi tej sytuacji. Otóż przez parametr ignore=400 powoduje, że szczegóły implementacji, tu konkretnie warstwy transportowej HTTP, przechodzą do warstwy wyższej. Program korzystający z biblioteki musi obsługiwać kody specyficzne dla warstwy niższej, co powoduje bardzo problematyczną zależność. Dlaczego to takie ważne?

Status 400 HTTP oznacza BadRequest, czyli wskazuje na problem żądania wygenerowanego przez klienta HTTP. Może to być zła składnia żądania HTTP, może to być błąd walidacji (nieprawidłowe dane w prawidłowo składniowo sformułowanym żądaniu), albo tak jak w omawianym przypadku - istnienie konkretnego zasobu. Zatem status 400 może mieć wiele znaczeń, i nawet jeśli teraz ma tylko jedno, to nie ma żadnej gwarancji, iż w kolejnych odsłonach HTTP API nie nabierze dodatkowych. Status o kodzie 400 nie oznacza, że indeks istnieje. Oznacza, że serwer zwrócić odpowiedź o kodzie 400, czyli uznaje że problem leży w żądaniu klienta, gdzie klient może powtórzyć odpowiednio zmodyfikowane żądanie, a przyczyny mogą być różne. Ale jak klient zmodyfikuje żądanie, skoro dokładnie nie określono przyczyny?

Kolejnym problemem jest sam flow programu, w którym zawiera się wywołanie es.indices.create(index='my-index', ignore=400). W przypadkach błędu utworzenia indeksu, niekoniecznie związanego z jego istnieniem po stronie serwera, program będzie wykonywał się dalej. To oznacza, że błąd programu wystąpi w innym miejscu, a konkretnie podczas próby wykonania operacji na indeksie, który nie istnieje.

Złamanie zasad hermetyzacji powoduje, że zależność z niższą warstwą transportową tworzy kod aplikacji, który jest trudniejszy do utrzymania. Kod 400 nie jest jednoznaczny, nie jest dostatecznie czytelny, a w przypadku zmian w warstwie transportowej nie będzie kompatybilny wstecznie.

Interfejs biblioteki jest niespójny, ponieważ programista nie ma wpływu na szczegóły żądania HTTP wysyłanego do serwera, ale musi operować na częściowych informacjach z odpowiedzi HTTP. To nie tylko nie czyni programowania łatwym ani elastycznym, ale przede wszystkim forsuje programowanie w błędnym stylu, wskutek czego powstające aplikacje w oparciu o biblioteki tej jakości będą o wiele bardziej błędne.

Poprawne podejście do zagadnienia powinno być oparte o wyjątki. Powyższy przykład, w bibliotece o dobrze zaprojektowanym interfejsie, powinien wyglądać tak:

es = ElasticSearch()

try:
    es.indices.create(index='my-index')
except IndexAlreadyExists:
    # obsługa sytuacji, w której indeks już istnieje
except IndexCreationError:
    # obsługa błędu przy próbie tworzenia indeksu (złe parametry, inne)

Kod jest niewiele dłuższy, ale za to czytelny oraz niezależny od szczegółów implementacji warstwy transportowej.

Jeśli intencją autorów było ulokowanie biblioteki klienckiej w warstwie transportowej, to sens istnienia tej biblioteki w tej formie jest właściwie żaden. Czym bowiem różni się zapytanie es.indices.create('my-index') od requests.put('http://elasticsearch-instance/my-index')? Czyż nie wystarczyłoby utworzyć niewielki adapter, który zawierałby adresy węzłów klastra, implementację sniffera czy strategii wysyłania żądań, tworząc z niego klienta stricte transportowego, ułatwiającego komunikację HTTP z usługą ElasticSearch?

elasticsearch-dsl

Biblioteka elasticsearch-dsl jest adapterem wysokiego poziomu. Dostarcza interfejs bardziej obiektowy i przyjazny programowaniu aplikacji. Podstawową klasą, z którą programiści aplikacji mają do czynienia, jest klasa Search. Swoją konstrukcją przypomina klasę QuerySet z frameworka Django, i zapewne była ona wzorem dla autorów biblioteki. Nie ustrzegli się jednak błędów projektowych.

W oryginale, tj. QuerySet, metody zwracają referencję na zmodyfikowane kopie instancji. Dzięki temu podejściu można stosować łańcuchowanie zapytań do baz danych, używać fragmentów łańcuchów, tworzyć warunkowe konstrukty, itd. Programista ma pewność, że każdy obiekt zwrócony przez te metody jest nowym obiektem, a stan oryginalnej instancji klasy QuerySet nie jest modyfikowany.

Programiści elasticsearch-dsl stoją jednak w koncepcyjnym rozkroku. Podstawowe metody kalsy Search istotnie zwracają zmodyfikowane kopie, co widać na poniższym przykładzie:

>>> s1 = Search()
>>> s2 = Search().filter(foo='bar')

>>> id(s1) == id(s2)
False

>>> s1.to_dict()
{}

>>> s2.to_dict()
{'query': {'bool': {'filter': [{'match_all': {'foo': 'bar'}}]}}}

Jednak już przy agregacjach wpadli w sidła niekonsekwencji, i obiekt klasy Search zmienia swój stan in place:

>>> s1.aggs.bucket('per_tag', 'terms', field='tags')
>>> s1.to_dict()
{'aggs': {'per_tag': {'terms': {'field': 'tags'}}}}

Oczywiście można uzasadnić, że skoro metoda .bucket() jest wywoływana na właściwości aggs, to zmodyfikowana zostanie instancja klasy opisującej agregacje (tu: AggsProxy), co pośrednio wpływa na stan instancji klasy Search. Jednak nie jest do końca czytelne, co dzieje się ze stanem Search. Nie wiadomo czy oraz jak zmiany aggs wpływają na stan instancji nadrzędnej klasy Search, a znając pierwotną konwencję intuicyjnie spodziewamy się kopii.

Zastosowana tutaj asocjacja między klasami, jest niepotrzebnie wyeksponowana do interfejsu publicznego nadrzędnej klasy Search, co wpływa na niespójne zachowanie interfejsu wysokiego poziomu. Jest to pogwałcenie nie tylko zasad hermetyzacji, ale także dobrych i prostych zasad PEP20 - The Zen of Python.

Prawidłowy publiczny interfejs do modyfikacji agregacji powinien eksponować odpowiednie metody.

Przykład:

>>> s1.agg_bucket('per_tag', 'terms', field='tags')

gdzie metoda agg_bucket zwracałaby kopię s1z kopią zmodyfikowanego obiektu opisującego agregacje, lub interfejs uogólniony działający na podobnej zasadzie:

>>> s1.aggregate(A(...))

Siedem wersji architektonicznego bólu i jeden plus

Obydwie biblioteki są już w siódmej odsłonie (7.x). Przez wiele lat nie zrobiono nic ku wyeliminowaniu błędów projektowych. Wręcz przeciwnie - autorzy brną dalej w tym samym kierunku.

Chyba tylko z powodu utopijnej wizji oraz braku mocy przerobowych, biblioteka niższego poziomu jest w większości generowana automatycznie z API HTTP, co tylko dowodzi braku sensu jej istnienia w takiej formie. Wygenerowany kod musi pokrywać całe API HTTP - wszystkie operacje. Ale po co, skoro i tak narzuca interakcję z częścią specyficzną dla warstwy transportowej? Po co, skoro kod biblioteki jest w części generowany z API HTTP, przez co i tak nie zapewni kompatybilności wstecznej, gdyby interfejs HTTP się zmienił?

W praktyce wraz z aktualizacją serwera ElasticSearch i tak trzeba aktualizować obydwie biblioteki, co dowodzi braku spójnego i dobrego konceptu na te interfejsy. Dla kontrprzykładu Django nie musi być aktualizowane, żeby pracować z różnymi wersjami PostgreSQL, np. od wersji 9 do 12. Dopiero gdy chcemy z poziomu interfejsu obiektowego dostać się do nowych funkcji bazy danych, może okazać się konieczne uaktualnienie wersji frameworka. Ale nie powstaje Django 12 dla dwunastej odsłony bazy danych.

Opisywane tu biblioteki są, bo po prostu muszą istnieć. Ze względu na wsparcie dla istniejących projektów oraz przez to, że są "standardem" komunikacji z ElasticSearch z poziomu Pythona. Nie są jednak interfejsem dobrym. Ich zaletą jest to, że w ogóle są. Można z nich korzystać, a ze względu na specyfikę komunikacji z serwerem nawet trzeba, ale najlepiej ze świadomością istniejących pułapek. Podane we wpisie przykłady są "pierwszymi z brzegu", a problemów tej kategorii jest więcej. Niniejszy wpis ma za zadanie wyczulić programistów na problem oraz pokazać na tych przykładach, jak nie powinno się projektować interfejsów.