Test webhook/callback on your local environment

I had a requirement that said sending out SMS messages which come delivery status. I was looking at Twilio and they provide status callback.

As you know that callback API must be standing out on public to receive requests from any 3rd party services and my local environment is sitting behind my home router which is private. The old fashioned solution would be a port forwarding but there’s the service called ngrok.

ngrok provides public URLs for exposing your local web servers, testing on mobile devices, building webhook/callback integration, etc. Good thing is free for 1 process, 4 tunnels, and 40 connections. That is enough for me for testing my implementation.

Once you created an account, you will see the instructions on the dashboard.

Set Up

  1. Download and unzip the ngrok application on your machine.
  2. Connect your account.
    $ ./ngrok authtoken XXXXXXXX
  3. Fire it up and Boom!
    $ ./ngrok http 8000

Below is the screenshot.

ngrok by @inconshreveable                                                                                   (Ctrl+C to quit)

Session Status                online
Account                       user@example.com (Plan: Free)
Version                       2.3.35
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://236168b0.ngrok.io -> http://localhost:9001
Forwarding                    https://236168b0.ngrok.io -> http://localhost:9001

Connections                   ttl     opn     rt1     rt5     p50     p90
                              18      0       0.00    0.00    0.03    10.48

HTTP Requests
-------------

POST /notification/sms/callback/ 200 OK
POST /notification/sms/callback/ 200 OK
POST /notification/sms/callback/ 500 Internal Server Error
POST /notification/sms/callback/ 500 Internal Server Error
GET  /notification/sms/callback/ 405 Method Not Allowed
GET  /notification/sms/callback/ 405 Method Not Allowed

Every time the ngrok runs, it generates the random computer name.
i.e. https://236168b0.ngrok.io

If you want a reserved address, you need to upgrade to one of paid account.

Web UI

ngrok provides a real-time Web UI where you can introspect all of the HTTP traffic running over your tunnels. After you’ve started ngrok, just open http://localhost:4040 in a web browser to inspect request details.

Replaying requests

There’s the button called Replay on the right top of the screen. This allows you to replay any request and also allows modifications.

Screen Shot 2020-05-21 at 11.40.06 AM

Conclusion

  1. Easy to setup.
  2. Getting random IDs might be a silly dog. Feed him.
  3. Web UI for inspecting all traffics.

 

How to Set the Denon AVR AirPlay password?

black speaker close up photography

Photo by Marinko Krsmanovic on Pexels.com

I’ve got a Denon AVR-X1400H 7.2 Receiver and I tried to play music from my iPhone and Macbook but I could not make it work because it kept asking me to enter the AirPlay Password I have never set up.

I tried to google and found one reference.

  1. From the Denon Receiver(on TV), go to SETUP > Network > Connection.
  2. Select Wireless(Wi-Fi).
  3. Choose Wi-Fi Setup.
  4. Select Share Wi-Fi setting from an iOS device.
    NOTE: iOS 7 or later is required. It explains what to do next on TV as well.
  5. From your iPhone, go to Settings > Wi-Fi.
  6. Select your Denon Receiver under SET UP NEW AIRPLAY SPEAKER.
    1. It takes about 5-10 seconds to show up the receiver on the list. Just be patient.
    2. Probably, you will see Denon AVR-X1400H under the list. In my case, I could see Living Room because I changed the name of the receiver.
      IMG_8689
  7. Select your Wi-Fi network.
  8. Set your speaker password. << This step is important and it is the AirPlay password you are looking for.
    IMG_8690
  9. Once you hit Next button, your receiver will automatically setup the Wi-Fi connection from your iPhone.
  10. Hit Done on your iPhone as well as on your receiver.
  11. Enjoy the AirPlay mode.

pyenv: Python Version Management

person using macbook pro

Photo by Christina Morillo on Pexels.com

pyenv does:

  1. Let you change the global Python version on a per-user basis.
  2. Provide support for per-project Python versions.
  3. Allow you to override the Python version with an environment variable.
  4. Search commands from multiple versions of Python at a time. This may be helpful to test across Python versions with tox.

 

Installation

For Mac OS, use homebrew.

$ brew update
$ brew install pyenv

 

Install virtualenv or virtualenvwrapper.

$ brew install pyenv-virtualenv
# or
$ brew install pyenv-virtualenvwrapper

Add the following configurations to the profile file.

# pyenv
# Load pyenv automatically by adding

# the following to ~/.bashrc: export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

# Add this if virtualenv is used
export PATH=$(pyenv root)/shims:$PATH
# For pyenv-virtualenvwrapper
export PYENV_VIRTUALENVWRAPPER_PREFER_PYVENV="true"
# virtualenvwrapper
export WORKON_HOME=~/venv
export PROJECT_HOME=~/venv
pyenv virtualenvwrapper_lazy

 

Examples

$ pyenv versions
  system
* 3.6.10 (set by /Users/dcho/.pyenv/version)
  3.6.8
  3.7.6

$ python --version
Python 3.6.10

$ pyenv global 3.7.6

$ python --version
Python 3.7.6

$ pyenv versions
  system
  3.6.10
  3.6.8
* 3.7.6 (set by /Users/dcho/.pyenv/version)

야드 관리와 툴

3월이 되면 잠자던 나무와 꽃들이 기지개를 피고 일어날 준비를 시작한다. 이때쯤 되면 겨우내 눈, 비와 바람에 날린 꺾인 나뭇잎과 가지들을 치워야 하고, 노랗게 죽은 잔디들을 솎아내 버려야 한다.

올해는 큰맘먹고 아마존에서 Dethatcher를 구입했는데 작년 초부터 말썽인 이끼들이 너무 번져 잔디들이 죽어가고 있기 때문이었다. 이 Dethatcher는 Rake를 대신하는 기계인데, 죽은 잔디, 이끼, 낙옆, 잔가지들을 솎아내주면서 땅을 긁어서 부드럽게 만들어준다. 이렇게 땅을 부드럽게 만들게 되면 잔디 씨와 비료를 뿌리기에 좋다.

이른 봄에 해야할 일들을 정리해봤다.

  1. 마당에 있는 큰 나뭇가지들을 치운다.
  2. Dethatcher로 죽은 잔디 등을 솎아낸다.
  3. Rake로 죽은 잔디 등을 긁어 모아 치운다.
  4. Leaf Blower로 야드를 깨끗이 한다.
  5. 잔디 씨(Over seeding)를 뿌린다.
  6. 비료(Fertilizer)를 뿌린다.
  7. 물을 준다.

잔디 씨를 뿌리면 2주동안 매일 땅을 촉촉하게 할 정도의 물을 줘야 하고, 비료는 1년에 2-3번 정도 주면 되는 데, 이른 봄에 한번 여름에 한번 가을에 한 번 주는 것이 좋다. 이번에 코스트코에서 구입한 잔디 씨는 비료와 같이 들어있는 제품이어서 #5 와 #6을 한방에 해결하였는데 처음써본 제품이라 만족도는 몇 주 지나봐야 알 것 같다.

필요에 의해 Lime도 뿌려줘야 하는데 이는 땅의 Ph를 맞춰주는 역할을 한다. 땅은 갈수록 산성이 되어가는데, 이렇게 산성이 되면 이끼가 쉽게 모습을 드러내기 때문에 알칼리성인 Lime을 뿌려주어 Ph를 맞추는 것이 좋다. 라임을 뿌리는 시기는 늦가을이 가장 좋고 이른 봄에 뿌려도 무방하다. 소나무가 있는 곳은 소나무잎때문에 땅의 산성도가 다른곳에 비해 높다. 잔디가 잘 자라는 Ph의 Level은 아주 약한 산성인 6.5가 가장 좋은데 Ph를 잘 맞추면 풍성한 잔디를 기를 수 있게 된다.

처음 야드 관리를 하는것이라면 필요한 툴들이 많이 있을텐데, 경제적으로 풍요롭지 않다면 꼭 필요한 툴 부터 구매해 나가는 것이 좋다. 나 또한 아래 툴 들을 모으는데 2-3년이 되었다. 야드 관리 업체를 1년 사용하는 비용은 대략 2-3천불 되고, 아래 툴 들을 구매하는 비용과 비슷하기 때문에 일년 해보면 본전 뽑는것이다.

해마다 필요한 물품들

  1. Grass Seed
    마당 넓이에 따라 크기 맞게 구매. 참고로 40lbs 짜리가 가성비 좋음.
    이른 봄이나 가을에 뿌리는 것이 좋음.
    https://www.homedepot.com/p/Scotts-40-lb-Turf-Builder-Grass-Seed-Sun-and-Shade-Mix-18334/203760900
  2. Patch용 Grass Seed (필요시에만 구매)
    4/17/2019 까지 세일 하는 가성비 최고의 패치용 잔디 씨 + 퍼틸라이저 + Soil
    이른 봄이나 가을에 뿌리는 것이 좋음.
    https://www.lowes.com/pd/Scotts-20-lb-Sun-And-Shade-Lawn-Repair-Mix/1000042425
  3. Fatilizer
    1년에 3번 뿌려주는 것이 가장 좋은데 2번 정도만 해도 괜찮은 듯 함. 종류가 너무 많고 잡초방지제와 같이 들어있는 제품들도 많이 있는데 내가 선호하는 제품은 유기농 비료이다.
    이른 봄에 한번, 여름에 한번, 가을에 한번 뿌리면 됨.
    https://www.homedepot.com/p/Milorganite-36-lb-Slow-Release-Nitrogen-Fertilizer-100048741/100618523
  4. Lime
    땅의 Ph를 맞춰주는 건데 땅이 점점 산성이 되면 한번씩 뿌려주면 됨. 대충 2-3년에 한번 뿌리면 될 듯.
    땅이 산성이 되면 이끼가 끼기 시작하는데 이끼의 생존력이 강해서 몇 달 안에 이끼밭이 됨. 이끼가 보이면 땅이 산성이 됐다는 의미니까 라임은 꼭 뿌리길.
    이른 봄이나 가을에 뿌림.
    https://www.homedepot.com/p/Oldcastle-Green-n-Grow-30-lb-Specially-Formulated-Limestone-54055018/100658972
  5. 이끼제거제(필요시에만 구매)
    위에도 말했듯이 땅이 산성이 된 경우 이끼가 보이기 시작하는데 산불과 이끼는 초기에 진압(?)해야 한다.
    https://www.homedepot.com/p/Scotts-5-000-sq-ft-Moss-Control-Granules-31015/204758942
  6. 목장갑
    코스트코에서 10쌍들이 한 묶음 구매.

필요한 툴들

  1. Rakes
    https://www.homedepot.com/p/Razor-Back-24-Tine-Steel-Rake-2915200/204476216
  2. Spreader
    https://www.homedepot.com/p/Scotts-Turf-Builder-Mini-Broadcast-Spreader-76121/100664982
  3. Lawn Mower
    Recommended: https://www.homedepot.com/p/EGO-21-in-56-Volt-Lithium-ion-Cordless-Walk-Behind-Self-Propelled-Mower-Kit-7-5-Ah-Battery-Charger-Included-LM2102SP/206515944
    https://www.homedepot.com/p/EGO-21-in-56-Volt-Lithium-ion-Cordless-Battery-Walk-Behind-Push-Mower-5-0-Ah-Battery-Charger-Included-LM2101/206515766
  4. String Trimmer
    https://www.homedepot.com/s/ego%2520string%2520trimmer?NCNI-5
    Recommended: https://www.homedepot.com/p/EGO-15-in-56-Volt-Lithium-ion-Electric-Cordless-String-Trimmer-w-Rapid-Reload-Head-with-2-5Ah-Battery-and-Charger-Included-ST1502SF/300638783
  5. Leaf Blower
    무조건 백팩용 가스 추천. 베터리는 파워가 약하고 충전 주기가 짧아서 사용하기 힘듦. 베터리용의 장점은 딱 하나. 가벼워서 포터블해 드라이브웨이 정도의 단시간에 쉽게 블로잉 할 수 있는곳에 적합.
    가장 좋은건 EGO 베터리용 블로어 하나랑 ECHO 가스 백팩 블로어 하나씩 있음 최고!!
    https://www.homedepot.com/s/echo%2520blower?NCNI-5
    Recommended: https://www.homedepot.com/p/ECHO-215-MPH-510-CFM-58-2cc-Gas-2-Stroke-Cycle-Backpack-Leaf-Blower-with-Tube-Throttle-PB-580T/205894359
  6. Loppers
    아래 Hedge Trimmer는 잔 가지를 예쁘게 정리할 때 사용하지만 Loppers는 조금 굵은 가지를 자르는데 사용된다.
    https://www.homedepot.com/p/Fiskars-1-1-2-in-Cutting-Capacity-Steel-Blade-Loppers-376101/308132145
  7. Hedge Shears
    a.k.a. 전지가위. 아래 Hedge Trimmer나 전기가위 둘 중 하나는 기본.
    https://www.homedepot.com/p/Fiskars-9-in-Power-Lever-Steel-Blade-Telescoping-Steel-Handle-Hedge-Shears-391690/205617374

있으면 좋은 툴들

  1. Dethatcher
    죽은 잔디 솎아주고 땅을 갉아줘서 스프링 클린업에 좋은 툴이고, 이걸로 정리하고 난 다음 씨 뿌리면 됨.
    https://www.amazon.com/gp/product/B0030BG1HM/ref=ppx_yo_dt_b_asin_title_o07_s00?ie=UTF8&psc=1
  2. Hedge Trimmer
    가지치기(전지) 용 톱인데 싼 제품들을 써도 무난하다. 탱탱한 팔 근육을 원한다면 스킵.
    https://www.homedepot.com/p/BLACK-DECKER-24-in-3-3-Amp-Corded-Electric-Hedge-Hog-Trimmer-with-Rotating-Handle-HH2455/202590054
  3. Sprayer
    주로 페이브나 원하지 않는 곳에 난 잡초들을 제거할때 제초제를 뿌리는데 제초제에 전동형 스프레이어가 달린 제품을 구입하는 경우엔 필요가 없지만 그렇지 않은 경우에는 필요한 제품.
    https://www.homedepot.com/p/Scotts-1-Gal-Multi-Use-Sprayer-190498/300259157

여름과 가을에 해야할 일들도 정리를 해서 업뎃을 할 예정이다.

Nginx stops by itself once in a while.

Nginx stops by itself occasionally and didn’t know the reason for it. When the server got down at 3rd time, I decided to dig into the problem.

I’ve looked up syslog first.

tail -5000f /var/log/syslog

Found the following messages.

May 12 00:51:39 ip-172-31-12-44 systemd[1]: Starting Certbot...
May 12 00:51:40 ip-172-31-12-44 systemd[1]: Stopping A high performance web server and a reverse proxy server...
May 12 00:51:40 ip-172-31-12-44 systemd[1]: Stopped A high performance web server and a reverse proxy server.
May 12 00:51:46 ip-172-31-12-44 certbot[15472]: nginx: [error] open() "/run/nginx.pid" failed (2: No such file or directory)
May 12 00:51:47 ip-172-31-12-44 systemd[1]: Starting A high performance web server and a reverse proxy server...
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
May 12 00:51:47 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
May 12 00:51:48 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)
May 12 00:51:49 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
May 12 00:51:49 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
May 12 00:51:49 ip-172-31-12-44 nginx[15559]: nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)
May 12 00:51:49 ip-172-31-12-44 nginx[15559]: nginx: [emerg] still could not bind()
May 12 00:51:49 ip-172-31-12-44 systemd[1]: nginx.service: Control process exited, code=exited status=1
May 12 00:51:49 ip-172-31-12-44 systemd[1]: Failed to start A high performance web server and a reverse proxy server.
May 12 00:51:49 ip-172-31-12-44 systemd[1]: nginx.service: Unit entered failed state.
May 12 00:51:49 ip-172-31-12-44 systemd[1]: nginx.service: Failed with result 'exit-code'.
May 12 00:51:49 ip-172-31-12-44 certbot[15472]: Hook command "service nginx start" returned error code 1
May 12 00:51:49 ip-172-31-12-44 certbot[15472]: Error output from service:
May 12 00:51:49 ip-172-31-12-44 certbot[15472]: Job for nginx.service failed because the control process exited with error code. See "systemctl status nginx.service" and "journalcte>-xe" for details.

I remember that I’ve set up a nginx server with certbot and noticed that it’s complaining about restarting nginx during certification renewal process.

Thus, I opened up the certification configuration file as below.

sudo vi /etc/letsencrypt/renewal/mydomain.com.conf

I’ve changed the config file as below.

  1. Update authenticator from standalone to nginx.
  2. Update installer from nginx to none
  3. Comment out pre_hook.
  4. Update post_hook with service nginx restart.
# Options used in the renewal process
[renewalparams]
authenticator = nginx
installer = none
account = IDENTIFIER
#pre_hook = service nginx stop
post_hook = service nginx restart

To test the update, run the following command in order to simulate the renewal.

sudo certbot renew --dry-run

If it results without issues or errors, it’s good to go. 🙂

virtualenvwrapper configuration

There are files for hooks and configuration is very straight forward.
I’d like to create a new folder named src for storing all the code base. Then, change directory to the new folder. Let’s see what I can do.

There is postrmvirtualenv file under $WORKON_HOME. In my case, it is ~/venv.
Let’s open up the file in vi editor and add some hooks.

$ vi ~/venv/postrmvirtualenv

# Add following to the file and save.
path=$VIRTUAL_ENV/src
mkdir $path
cd $path

I’ve created a variable for the path of the source folder I mentioned. Then, create it and change directory to it.

Now, mkvirtualenv command lets you to create a new virtual env box and create a new folder called src under the project just created and change directory to the folder.

Let’s make some changes for workon command same as above.

I’d like to change directory to the src folder under the project once the project has been activated. Also, I’d like to change directory to the virtualenv root folder ~/venv once the project has been deactivated.

Open up postactivate and add below for activate behavior.

cd $VIRTUAL_ENV/src

Open up postactivate and add below for activate behavior.

cd $WORKON_HOME

That’s it and enjoy.

git setup with multiple keys(accounts)

I had 2 accounts in gitlab.com and I couldn’t use one rsa key to access both accounts. It seems like gitlab’s restriction.

So, I needed to create another key for the 2nd account. Let’s try.

  1. Generate a new key
    $ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
    Generating public/private rsa key pair.
    Enter file in which to save the key (/Users/myid/.ssh/id_rsa): /Users/myid/.ssh/new_rsa
    Enter passphrase (empty for no passphrase):
    Enter same passphrase again:
    Your identification has been saved in /Users/myid/.ssh/new_rsa.
    Your public key has been saved in /Users/myid/.ssh/new_rsa.pub.
    
  2. Update ssh config file
    $ vi ~/.ssh/config
    Host gitlab-com-new
      HostName gitlab.com
      User git
      IdentityFile ~/.ssh/new_rsa
    
  3. Add the new key created to ssh agent
    $ ssh-add ~/.ssh/new_rsa
  4. Add or update the git repository path
    $ git remote add origin git@gitlab-com-new:new_id/new_repository.git
    # or if the repository already exists
    $ git remote set-url origin git@gitlab-com-new:new_id/new_repository.git
  5. Enjoy 😛

Getting the last sub entries per entry

Now, I am trying to get the sizes of the last created versions of each contents.

Here’s the simple model diagram.

content_version_diagram

It looks just like a blog post and its comments. Here’s sample data.

Content model

id name
1 My Content 1
2 My Content 2

Version model

id parent_id size
1 1 100
2 1 200
3 1 300
4 2 400
5 2 500

The records with grayed background would be the result set as following table.

Expected result set (Versions only)

id parent_id size
3 1 300
5 2 500

 

My first thought was using group by with Max() function which requires a join or sub query. The 2nd thought was using distinct, but I wasn’t sure that grabbing the last ones only per parent. Anyway, here are the django orm and the sql queryset generated.
I tested with django-debug-toolbar’s debugsqlshell command which shows sql query strings per command with execution times. Below, I discard all execution times from the result because the result may vary based on the performance of the machine tested with.


$ ./manage.py debugsqlshell

>>> Version.objects.distinct('parent').order_by('parent_id', '-created_at')
SELECT DISTINCT ON ("content_version"."parent_id") "content_version"."id",
                   "content_version"."parent_id",
                   ...
FROM "content_version"
ORDER BY "content_version"."parent_id" ASC,
         "content_version"."created_at" DESC

Now I need to get size of each versions.

>>> Version.objects.filter(parent__owner=owner).distinct('parent').order_by('parent_id', '-created_at')
SELECT DISTINCT ON ("content_version"."parent_id") "content_version"."id",
                   "content_version"."parent_id",
                   ...
FROM "content_version"
INNER JOIN "content_content" ON ("content_version"."parent_id" = "content_content"."id")
WHERE ("content_version"."is_archived" = false
       AND "content_content"."is_archived" = false
       AND "content_content"."owner_id" = '65ed9958-788e-11e6-9b70-a820663cfc41'::uuid)
ORDER BY "content_version"."parent_id" ASC,
         "content_version"."created_at" DESC

Now I need to add a filter to getting none-archived records and owned by a user.

>>> Version.objects.filter(parent__owner=owner).distinct('parent').order_by('parent_id', '-created_at').values_list("size", flat=True)
SELECT DISTINCT ON ("content_version"."parent_id") "content_version"."size"
FROM "content_version"
INNER JOIN "content_content" ON ("content_version"."parent_id" = "content_content"."id")
WHERE ("content_version"."is_archived" = false
       AND "content_content"."is_archived" = false
       AND "content_content"."owner_id" = '65ed9958-788e-11e6-9b70-a820663cfc41'::uuid)
ORDER BY "content_version"."parent_id" ASC,
         "content_version"."created_at" DESC

So far so good. 🙂
Oh, Now I want to get the total size of all the last versions.
I tried to add aggregation to get Sum() of size, and the django's orm results broken queries. If anyone knows how to get the result with django's orm, please let me know. I may buy you a cup of coffee or beer sometime.

Anyway, so I decided to use raw sql for the result and I didn't want to use python's sum() function because the number of records was arbitrary and may get lots of records that would make the application slow down.

Here's the last code snippet I used.


def get_storage_bytes_used(owner):
    """
    Returns total bytes used for user.
    The calculation is Sum of latest versions of each `Content` records.

    Args:
        owner (User): An owner of Content object.

    Return:
        bytes_used (int): Total bytes used for a given user.
    """
    query = '''SELECT sum(size) from (
        SELECT DISTINCT ON (v."parent_id") v."size"
        FROM "content_version" AS v
        INNER JOIN "content_content" AS c ON (v."parent_id" = c."id")
        WHERE (v."is_archived" = false
            AND c."is_archived" = false
            AND c."owner_id" = '%(owner_id)s'::uuid)
        ORDER BY v."parent_id" ASC, v."updated_at" DESC) as t'''
    context = {'owner_id': owner.id}

    with connection.cursor() as c:
        c.execute(query % context)
        bytes_used = c.fetchone()

    return bytes_used[0] or 0

Happy coding.