├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ └── publish.yml ├── .gitignore ├── .ruby-version ├── 404.md ├── CNAME ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── _activities ├── 5-things-to-learn-python.md ├── all-people-suck-at-code.md ├── android-dev-podcast.md ├── apps-conf-server-languages.md ├── are-software-title-inflating-devto.md ├── chat-bots-vc-ru.md ├── choosing-your-first-programming-language-tceh.md ├── developers-view-on-hiring.md ├── devman-linter-workshop.md ├── devoops-discussion-2020-devsecops.md ├── devoops-interview-2019.md ├── devoops-interview-2020-devsecops.md ├── django-chat-stubs.md ├── django-girls-moscow.md ├── django-rest-framework-devman.md ├── dry-python-returns-015-announcement.md ├── dump-code-review-2019.md ├── girls-for-tech-numa.md ├── github-action-hero.md ├── github-hackathon-2020.md ├── github-planet-github-stars.md ├── github-planet1.md ├── habr-formulas-for-homebrew.md ├── harb-interview-for-inteviewers-habr.md ├── hexlet-open-source.md ├── hexlet-public-interview.md ├── hexlet-python-future.md ├── hexlet-python-packaging.md ├── hexlet-teams-and-control.md ├── holywars-about-linters-habr.md ├── how-to-find-clients.md ├── index-podcast-github.md ├── index-podcast-vacancies.md ├── moscow-python-conf-2020-article-evil.md ├── moscow-python-conf-2020-interview-filonov.md ├── moscow-python-conf-2020-interview-volkova.md ├── moscow-python-conf-2020-interview-voronov.md ├── moscow-python-podcast-all-stars1.md ├── moscow-python-podcast-blameless-environment.md ├── moscow-python-podcast-django.md ├── moscow-python-podcast-hr.md ├── moscow-python-podcast-knowledgeconf.md ├── moscow-python-stream-levelup1.md ├── moscow-python-stream-python-levelup.md ├── moscow-python-stream-quarantine.md ├── opensource-findings-08-2019.md ├── opensource-findings-09-2019.md ├── podcast-init-dry-python.md ├── podcast-init-mimesis.md ├── podlodka-podcast.md ├── preaching-about-blameless-environment-vc.md ├── preaching-about-blameless-environment.md ├── profhack-mentie.md ├── pydev-of-the-week.md ├── python-quarantine-2020.md ├── python-quarantine-second-wave-2020.md ├── rails-club-interview-elixir.md ├── rails-club-interview-english.md ├── rails-club-interview-video.md ├── rails-club-interview.md ├── remote-vs-office.md ├── rpw-future-of-python.md ├── rpw-what-is-python.md ├── russian-python-week-2020-communities.md ├── russian-python-week-2020-formats1.md ├── russian-python-week-2020-formats2.md ├── russian-python-week-2020-stream.md ├── sdcast-about-python.md ├── sdcast-about-rsdp.md ├── searching-github-for-developers.md ├── silver-bullet-podcast-rit.md ├── smart-heads-podcast.md ├── tceh-python-communities.md ├── the-initial-commit-interview.md ├── tproger-frontend-backend.md ├── what-authorization-to-use-for-mobile-apps.md ├── why-learning-python.md └── you-dont-need-to-motivate-people-vc.md ├── _config.yml ├── _includes ├── analytics.html ├── anchors.html ├── disqus.html ├── favicons.html ├── footer.html ├── head.html ├── header.html └── read_time.html ├── _layouts ├── content.html ├── default.html ├── page.html ├── post.html └── talk.html ├── _pages ├── about.md ├── activities.html ├── subscribe.md └── talks.html ├── _posts ├── 2017-04-01-managing-djangos-settings.md ├── 2017-05-14-why-changes-in-phoenix-1-3-are-so-important.md ├── 2017-06-12-is-yarn-still-a-thing.md ├── 2017-07-08-testing-bash-applications.md ├── 2017-07-19-creating-slugs-for-ecto-schemas.md ├── 2017-08-23-instant-command-line-productivity.md ├── 2017-10-09-using-better-clis.md ├── 2018-03-13-mediocre-developer.md ├── 2018-10-21-real-python-contants.md ├── 2018-12-13-blameless-environment.md ├── 2019-01-10-announcing-dotenv-linter.md ├── 2019-01-21-how-to-fix-a-bug.md ├── 2019-01-31-simple-dependent-types-in-python.md ├── 2019-02-09-python-exceptions-considered-an-antipattern.md ├── 2019-02-23-engineering-guide-to-user-stories.md ├── 2019-03-03-announcing-docker-image-size-limit.md ├── 2019-03-10-enforcing-srp.md ├── 2019-03-30-from-flow-to-typescript.md ├── 2019-06-29-really-typing-vue.md ├── 2019-07-19-6-best-mac-apps.md ├── 2019-08-25-typechecking-django-and-drf.md ├── 2019-08-31-testing-mypy-types.md ├── 2019-10-13-complexity-waterfall.md ├── 2019-11-21-testing-django-migrations.md ├── 2020-02-02-typed-functional-dependency-injection.md ├── 2020-02-25-conditional-coverage.md ├── 2020-03-11-do-not-log.md ├── 2020-06-07-how-async-should-have-been.md ├── 2020-10-24-higher-kinded-types-in-python.md ├── 2021-02-28-make-tests-a-part-of-your-app.md ├── 2021-06-30-typeclasses-in-python.md └── 2021-12-31-paramspec-guide.md ├── _sass ├── base │ └── _reset.scss ├── components │ ├── _archives.scss │ ├── _article.scss │ ├── _page.scss │ └── _tag.scss ├── helpers │ ├── _mixins.scss │ └── _variables.scss ├── utilities │ ├── _layout.scss │ └── _separator.scss └── vendor │ └── _highlight.scss ├── _talks ├── belgorod-python-2020.md ├── ddd-evotion-online-2020.md ├── devconf-2018.md ├── devoops-2019.md ├── dumpconf-2019.md ├── elixir-lang-moscow-2016.md ├── expert-fridays-2018.md ├── fpconf-2016.md ├── fpconf-2017.md ├── funbox-code-quality-2021.md ├── github-planet-02-2022.md ├── github-planet3-2021.md ├── heisenbug-2019.md ├── hexlet-online-meetup-2020.md ├── index-conf-2018.md ├── itea-conf-2021.md ├── itgm-2018.md ├── itgm-2019.md ├── knowledge-conf-2019.md ├── krasnodar-dev-days-2018.md ├── moscow-python-2017.md ├── moscow-python-2018.md ├── moscow-python-2021.md ├── moscow-python-67-how-to-write-python-code.md ├── moscow-python-conf-2018.md ├── moscow-python-conf-2019.md ├── moscow-python-conf-2020.md ├── moscow-python-junior-2017.md ├── moscow-security-2017.md ├── moscow-vue-js-3.md ├── moscowjs-2018.md ├── piter-a11y-2019.md ├── podlodka-backend-crew-2021.md ├── pycon-russia-2021.md ├── python-barnaul-2021.md ├── rails-club-2017.md ├── rit-2019.md ├── rit-quality-conf-2019.md ├── russia-opensource-meetup-2021.md ├── russian-python-week-open-source.md ├── saint-apps-conf-2019.md ├── saint-prug-2017.md ├── sbp-sre-meetup-2019.md ├── softer-2018.md ├── teamlead-spb-meetup-2019.md ├── teamleadconf-2020.md ├── vladimir-tech-talks-2021.md └── write-the-docs-2018.md ├── assets └── images │ └── favicons │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── apple-icon-precomposed.png │ ├── apple-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── manifest.json │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ └── ms-icon-70x70.png ├── css └── main.scss ├── index.html ├── opensearch.xml └── robots.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | # Check http://editorconfig.org for more information 2 | # This is the main config file for this project: 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | indent_style = space 10 | insert_final_newline = true 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "02:00" 8 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build Jekyll site 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | - name: Setup Pages 20 | uses: actions/configure-pages@v5 21 | - name: Build 22 | uses: actions/jekyll-build-pages@v1 23 | - name: Upload artifact 24 | uses: actions/upload-pages-artifact@v3 25 | deploy: 26 | runs-on: ubuntu-latest 27 | needs: build 28 | steps: 29 | - name: Deploy to GitHub Pages 30 | id: deployment 31 | uses: actions/deploy-pages@v4 32 | environment: 33 | name: github-pages 34 | url: ${{ steps.deployment.outputs.page_url }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: http://goel.io/joe 2 | 3 | #####=== Jekyll ===##### 4 | 5 | _site/ 6 | .sass-cache/ 7 | 8 | #####=== Sass ===##### 9 | 10 | .sass-cache 11 | *.css.map 12 | 13 | #####=== OSX ===##### 14 | .DS_Store 15 | .AppleDouble 16 | .LSOverride 17 | 18 | # Icon must end with two \r 19 | Icon 20 | 21 | # Thumbnails 22 | ._* 23 | 24 | # Files that might appear on external disk 25 | .Spotlight-V100 26 | .Trashes 27 | 28 | # Directories potentially created on remote AFP share 29 | .AppleDB 30 | .AppleDesktop 31 | Network Trash Folder 32 | Temporary Items 33 | .apdisk 34 | 35 | #####=== Windows ===##### 36 | # Windows image file caches 37 | Thumbs.db 38 | ehthumbs.db 39 | 40 | # Folder config file 41 | Desktop.ini 42 | 43 | # Recycle Bin used on file shares 44 | $RECYCLE.BIN/ 45 | 46 | # Windows Installer files 47 | *.cab 48 | *.msi 49 | *.msm 50 | *.msp 51 | 52 | # Windows shortcuts 53 | *.lnk 54 | 55 | #####=== Linux ===##### 56 | *~ 57 | 58 | # KDE directory preferences 59 | .directory 60 | 61 | # Linux trash folder which might appear on any partition or disk 62 | .Trash-* 63 | 64 | #####=== JetBrains ===##### 65 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 66 | 67 | *.iml 68 | 69 | ## Directory-based project format: 70 | .idea/ 71 | # if you remove the above rule, at least ignore the following: 72 | 73 | # User-specific stuff: 74 | # .idea/workspace.xml 75 | # .idea/tasks.xml 76 | # .idea/dictionaries 77 | 78 | # Sensitive or high-churn files: 79 | # .idea/dataSources.ids 80 | # .idea/dataSources.xml 81 | # .idea/sqlDataSources.xml 82 | # .idea/dynamic.xml 83 | # .idea/uiDesigner.xml 84 | 85 | # Gradle: 86 | # .idea/gradle.xml 87 | # .idea/libraries 88 | 89 | # Mongo Explorer plugin: 90 | # .idea/mongoSettings.xml 91 | 92 | ## File-based project format: 93 | *.ipr 94 | *.iws 95 | 96 | ## Plugin-specific files: 97 | 98 | # IntelliJ 99 | out/ 100 | 101 | # mpeltonen/sbt-idea plugin 102 | .idea_modules/ 103 | 104 | # JIRA plugin 105 | atlassian-ide-plugin.xml 106 | 107 | # Crashlytics plugin (for Android Studio and IntelliJ) 108 | com_crashlytics_export_strings.xml 109 | crashlytics.properties 110 | crashlytics-build.properties 111 | 112 | 113 | ### Custom 114 | 115 | _drafts/ 116 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.5.1 2 | -------------------------------------------------------------------------------- /404.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: content 3 | title: "404: Page not found" 4 | sitemap: false 5 | --- 6 | 7 |

This page does not exist

8 |

Head back home!

9 | 10 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | sobolevn.me 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | group :jekyll_plugins do 4 | gem "jekyll", ">= 3.6.3" 5 | gem "ffi", ">= 1.9.24" 6 | gem "jekyll-sitemap", "~> 1.2.0" 7 | gem "jemoji", "~> 0.10.1" 8 | gem "jekyll-feed", "~> 0.11.0" 9 | gem "kramdown-parser-gfm" 10 | end 11 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (6.0.3.5) 5 | concurrent-ruby (~> 1.0, >= 1.0.2) 6 | i18n (>= 0.7, < 2) 7 | minitest (~> 5.1) 8 | tzinfo (~> 1.1) 9 | zeitwerk (~> 2.2, >= 2.2.2) 10 | addressable (2.8.0) 11 | public_suffix (>= 2.0.2, < 5.0) 12 | colorator (1.1.0) 13 | concurrent-ruby (1.1.8) 14 | em-websocket (0.5.2) 15 | eventmachine (>= 0.12.9) 16 | http_parser.rb (~> 0.6.0) 17 | eventmachine (1.2.7) 18 | ffi (1.14.2) 19 | forwardable-extended (2.6.0) 20 | gemoji (3.0.1) 21 | html-pipeline (2.14.0) 22 | activesupport (>= 2) 23 | nokogiri (>= 1.4) 24 | http_parser.rb (0.6.0) 25 | i18n (0.9.5) 26 | concurrent-ruby (~> 1.0) 27 | jekyll (3.9.0) 28 | addressable (~> 2.4) 29 | colorator (~> 1.0) 30 | em-websocket (~> 0.5) 31 | i18n (~> 0.7) 32 | jekyll-sass-converter (~> 1.0) 33 | jekyll-watch (~> 2.0) 34 | kramdown (>= 1.17, < 3) 35 | liquid (~> 4.0) 36 | mercenary (~> 0.3.3) 37 | pathutil (~> 0.9) 38 | rouge (>= 1.7, < 4) 39 | safe_yaml (~> 1.0) 40 | jekyll-feed (0.11.0) 41 | jekyll (~> 3.3) 42 | jekyll-sass-converter (1.5.2) 43 | sass (~> 3.4) 44 | jekyll-sitemap (1.2.0) 45 | jekyll (~> 3.3) 46 | jekyll-watch (2.2.1) 47 | listen (~> 3.0) 48 | jemoji (0.10.2) 49 | gemoji (~> 3.0) 50 | html-pipeline (~> 2.2) 51 | jekyll (~> 3.0) 52 | kramdown (2.3.1) 53 | rexml 54 | kramdown-parser-gfm (1.1.0) 55 | kramdown (~> 2.0) 56 | liquid (4.0.3) 57 | listen (3.4.1) 58 | rb-fsevent (~> 0.10, >= 0.10.3) 59 | rb-inotify (~> 0.9, >= 0.9.10) 60 | mercenary (0.3.6) 61 | mini_portile2 (2.5.1) 62 | minitest (5.14.3) 63 | nokogiri (1.11.4) 64 | mini_portile2 (~> 2.5.0) 65 | racc (~> 1.4) 66 | pathutil (0.16.2) 67 | forwardable-extended (~> 2.6) 68 | public_suffix (4.0.6) 69 | racc (1.5.2) 70 | rb-fsevent (0.10.4) 71 | rb-inotify (0.10.1) 72 | ffi (~> 1.0) 73 | rexml (3.2.5) 74 | rouge (3.26.0) 75 | safe_yaml (1.0.5) 76 | sass (3.7.4) 77 | sass-listen (~> 4.0.0) 78 | sass-listen (4.0.0) 79 | rb-fsevent (~> 0.9, >= 0.9.4) 80 | rb-inotify (~> 0.9, >= 0.9.7) 81 | thread_safe (0.3.6) 82 | tzinfo (1.2.9) 83 | thread_safe (~> 0.1) 84 | zeitwerk (2.4.2) 85 | 86 | PLATFORMS 87 | ruby 88 | 89 | DEPENDENCIES 90 | ffi (>= 1.9.24) 91 | jekyll (>= 3.6.3) 92 | jekyll-feed (~> 0.11.0) 93 | jekyll-sitemap (~> 1.2.0) 94 | jemoji (~> 0.10.1) 95 | kramdown-parser-gfm 96 | 97 | BUNDLED WITH 98 | 1.16.1 99 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nikita Sobolev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sobolevn's personal page 2 | 3 | [See it live!](https://sobolevn.me) 4 | 5 | 6 | ## Development 7 | 8 | Make sure you have `ruby 2.5.1` installed, use `rbenv` in case you don't. 9 | 10 | 1. `rbenv install` 11 | 2. `gem install bundler` 12 | 13 | Then run: 14 | 15 | 1. `bundle install` 16 | 2. `bundle exec jekyll serve --safe --strict_front_matter` 17 | 3. Done! 18 | 19 | 20 | ## License 21 | 22 | The source code is licensed under [MIT](https://github.com/sobolevn/sobolevn.github.io/blob/master/LICENSE). 23 | The content is licensed under [CC BY-NC](https://creativecommons.org/licenses/by-nc/4.0/). 24 | -------------------------------------------------------------------------------- /_activities/5-things-to-learn-python.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "5 things why you should learn Python" 4 | language: ru 5 | date: 2016-12-19 6 | link: https://tceh.com/post/put-pythonista-reursy-uspeha/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/all-people-suck-at-code.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "All people suck at writing code" 4 | language: ru 5 | date: 2018-09-28 6 | link: https://habr.com/ru/company/oleg-bunin/blog/424393/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/android-dev-podcast.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Android dev: about AppsConf" 4 | language: ru 5 | date: 2019-11-08 6 | link: https://www.youtube.com/watch?v=QoeDvHVoXlQ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/apps-conf-server-languages.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Why mobile developers need server languages" 4 | language: ru 5 | date: 2019-09-11 6 | link: https://habr.com/ru/company/oleg-bunin/blog/471070/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/are-software-title-inflating-devto.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Article 3 | title: "Are software titles inflating during the years?" 4 | language: us 5 | date: 2019-01-16 6 | link: https://dev.to/sobolevn/are-software-titles-are-inflating-during-the-years-n2a 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/chat-bots-vc-ru.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "How do you use Chatbots in your business?" 4 | language: ru 5 | date: 2019-05-02 6 | link: https://vc.ru/services/66319-chat-boty-chto-biznes-dumaet-o-virtualnyh-pomoshchnikah 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/choosing-your-first-programming-language-tceh.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Choosing your first programming language" 4 | language: ru 5 | date: 2016-01-15 6 | link: https://tceh.com/post/yazyk-dlya-programmista-novichka/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/developers-view-on-hiring.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Webinar 3 | title: "Developer's view on hiring" 4 | language: ru 5 | date: 2019-02-19 6 | link: https://www.itrecruiter.ru/nikita-sobolev 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/devman-linter-workshop.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Workshop 3 | title: "Workshop about Hactoberfest and WPS" 4 | language: ru 5 | date: 2019-10-24 6 | link: https://www.youtube.com/watch?v=Mg4tKuSFJTo 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/devoops-discussion-2020-devsecops.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "DevOops: What is DevSecOps?" 4 | language: ru 5 | date: 2020-12-03 6 | link: https://www.youtube.com/watch?v=EQ5NFBTIlQI 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/devoops-interview-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "DevOops Interview" 4 | language: ru 5 | date: 2019-10-29 6 | link: https://youtu.be/t-rjwlsa3nk?t=24486 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/devoops-interview-2020-devsecops.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "DevOops: Victoria Almazova and DevSecOps" 4 | language: us 5 | date: 2020-12-02 6 | link: https://youtube.com/watch?v=BXPlT9xvvgE 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/django-chat-stubs.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Django Chat: django-stubs" 4 | language: us 5 | date: 2021-12-08 6 | link: https://djangochat.com/episodes/django-stubs-nikita-sobolev 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/django-girls-moscow.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Course 3 | title: "Django Girls" 4 | language: ru 5 | date: 2016-02-06 6 | link: https://djangogirls.org/moscow1/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/django-rest-framework-devman.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Course 3 | title: "Django REST Framework Course" 4 | language: ru 5 | date: 2019-03-20 6 | link: https://dvmn.org/modules/django-rest-framework/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/dry-python-returns-015-announcement.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Video 3 | title: "returns@0.15 release announcement" 4 | language: ru 5 | date: 2020-09-09 6 | link: https://www.youtube.com/watch?v=jL-t0jxEM40 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/dump-code-review-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Article 3 | title: "Good code review practices" 4 | language: ru 5 | date: 2020-07-16 6 | link: https://habr.com/ru/company/it_people/blog/511316/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/girls-for-tech-numa.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Course 3 | title: "Girls 4 Tech" 4 | language: ru 5 | date: 2017-07-01 6 | link: http://girls4tech.ru/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/github-action-hero.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Github Action Hero" 4 | language: us 5 | date: 2020-07-17 6 | link: https://github.blog/2020-07-17-github-action-hero-nikita-sobolev/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/github-hackathon-2020.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Hackathon 3 | title: "Github Hackathon" 4 | language: us 5 | date: 2020-04-09 6 | link: https://github.blog/2020-04-09-featured-actions-from-the-github-actions-hackathon/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/github-planet-github-stars.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Stream 3 | title: "GitHub Planet: about GitHub Stars" 4 | language: ru 5 | date: 2021-03-16 6 | link: https://www.youtube.com/watch?v=I7DLJQyhEag 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/github-planet1.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Event 3 | title: "GitHub Planet: Russia" 4 | language: ru 5 | date: 2021-02-16 6 | link: https://www.youtube.com/watch?v=xddKLojmkus 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/habr-formulas-for-homebrew.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Article 3 | title: "Creating formulas for Homebrew" 4 | language: ru 5 | date: 2016-03-20 6 | link: https://habr.com/ru/post/279687/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/harb-interview-for-inteviewers-habr.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Article 3 | title: "Interview for interviewers" 4 | language: ru 5 | date: 2018-11-29 6 | link: https://habr.com/ru/post/431514/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/hexlet-open-source.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Hexlet: open-source" 4 | language: ru 5 | date: 2019-08-02 6 | link: https://ru.hexlet.io/blog/posts/vebinar-s-nikitoy-sobolevym-wemake-services-open-source-proekty 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/hexlet-public-interview.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Workshop 3 | title: "Hexlet: public interview with a junior dev" 4 | language: ru 5 | date: 2020-04-28 6 | link: https://www.youtube.com/watch?v=I-KhxNwc1Ns 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/hexlet-python-future.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Hexlet: future of Python" 4 | language: ru 5 | date: 2019-08-29 6 | link: https://ru.hexlet.io/blog/posts/programmirovanie-na-python-osobennosti-obucheniya-perspektivy-situatsiya-na-rynke-truda 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/hexlet-python-packaging.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Hexlet: Packaging, Repos, Security" 4 | language: ru 5 | date: 2019-05-23 6 | link: https://ru.hexlet.io/blog/posts/hexlet-podcast-pakety-repozitorii-bezopasnost 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/hexlet-teams-and-control.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Hexlet: Teams, Control, Offtop" 4 | language: ru 5 | date: 2019-06-07 6 | link: https://ru.hexlet.io/blog/posts/vebinar-s-nikitoy-sobolevym-wemake-services-komandnaya-razrabotka-i-eyo-kontrol 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/holywars-about-linters-habr.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Holywars about linters" 4 | language: ru 5 | date: 2019-02-13 6 | link: https://habr.com/ru/company/oleg-bunin/blog/433480/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/how-to-find-clients.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "How to find clients?" 4 | language: ru 5 | date: 2016-11-15 6 | link: https://vc.ru/ask/19985-problem-15509 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/index-podcast-github.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Index podcast: analyzing Github accounts" 4 | language: ru 5 | date: 2018-12-01 6 | link: https://soundcloud.com/indextalks/github-feat-aleksandr-borgart-nikita-sobolev 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/index-podcast-vacancies.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Index podcast: how to write great vacancies?" 4 | language: ru 5 | date: 2018-12-02 6 | link: https://soundcloud.com/indextalks/2-pishem-tekst-it-vakansii-featfeat-aleksandr-borgardt-i-nikita-sobolev 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-conf-2020-article-evil.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Article 3 | title: "MoscowPythonConf 2020: Pure evil Python" 4 | language: ru 5 | date: 2020-02-06 6 | link: https://habr.com/ru/company/oleg-bunin/blog/485960/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-conf-2020-interview-filonov.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "MoscowPythonConf 2020: About legacy code" 4 | language: ru 5 | date: 2020-01-30 6 | link: https://habr.com/ru/company/oleg-bunin/blog/486142/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-conf-2020-interview-volkova.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "MoscowPythonConf 2020: Machines should write tests" 4 | language: ru 5 | date: 2020-03-03 6 | link: https://habr.com/ru/company/oleg-bunin/blog/490670/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-conf-2020-interview-voronov.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "MoscowPythonConf 2020: About dependency hell" 4 | language: ru 5 | date: 2020-01-15 6 | link: https://habr.com/ru/company/oleg-bunin/blog/486142/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-podcast-all-stars1.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Moscow Python Podcast: all stars" 4 | language: ru 5 | date: 2020-06-13 6 | link: https://www.youtube.com/watch?v=XZF1XzX-K94 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-podcast-blameless-environment.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Moscow Python Podcast: culture" 4 | language: ru 5 | date: 2019-06-19 6 | link: https://podcast.python.ru/podcast/blameless-environment/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-podcast-django.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Moscow Python Podcast: about Django" 4 | language: ru 5 | date: 2018-11-16 6 | link: https://podcast.python.ru/podcast/kak-bystro-razvernut-proekt-s-nulya/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-podcast-hr.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Moscow Python Podcast: about open-source" 4 | language: ru 5 | date: 2018-09-12 6 | link: https://podcast.python.ru/podcast/vypusk-5-pro-opensors-balans-rabota-zhizn-i-pro-hr-ov/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-podcast-knowledgeconf.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Moscow Python Podcast: about business logic" 4 | language: ru 5 | date: 2019-06-06 6 | link: https://podcast.python.ru/podcast/from-knowledge-conference-2019/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-stream-levelup1.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Stream 3 | title: "Moscow Python Stream: about leveling up" 4 | language: ru 5 | date: 2021-02-05 6 | link: https://www.youtube.com/watch?v=Y3FXuKd9Ce8 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-stream-python-levelup.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Stream 3 | title: "Moscow Python Stream: Ilya Lebedev + Nikita Sobolev" 4 | language: ru 5 | date: 2021-03-25 6 | link: https://www.youtube.com/watch?v=bgFwdnCaT2I 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/moscow-python-stream-quarantine.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Stream 3 | title: "Moscow Python Stream: about ecosystem" 4 | language: ru 5 | date: 2020-11-20 6 | link: https://www.youtube.com/watch?v=llMvi3p50c0 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/opensource-findings-08-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Article 3 | title: "9 Best Opensource Findings for August 2019" 4 | language: ru 5 | date: 2019-09-02 6 | link: https://habr.com/ru/post/465855/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/opensource-findings-09-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Article 3 | title: "9 Best Opensource Findings for September 2019" 4 | language: ru 5 | date: 2019-10-02 6 | link: https://habr.com/ru/post/469753 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/podcast-init-dry-python.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Podcast.__init__: dry-python" 4 | language: us 5 | date: 2020-07-21 6 | link: https://www.pythonpodcast.com/dry-python-functional-programming-episode-272/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/podcast-init-mimesis.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Podcast.__init__: Mimesis" 4 | language: us 5 | date: 2018-03-30 6 | link: https://www.podcastinit.com/mimesis-with-nikita-sobolev-episode-155/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/podlodka-podcast.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Podlodka podcast about web frameworks" 4 | language: ru 5 | date: 2020-01-21 6 | link: http://podlodka.io/147 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/preaching-about-blameless-environment-vc.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Article 3 | title: "Preaching about blameless environment" 4 | language: ru 5 | date: 2019-08-02 6 | link: https://vc.ru/ontico/77803-blameless-environment-ili-pochemu-nelzya-vinit-lyudey-za-plohuyu-rabotu 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/preaching-about-blameless-environment.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Article 3 | title: "Preaching about blameless environment" 4 | language: ru 5 | date: 2019-08-01 6 | link: https://habr.com/ru/company/oleg-bunin/blog/462113/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/profhack-mentie.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Hackathon 3 | title: Profhack hackathon 4 | language: ru 5 | date: 2019-02-10 6 | link: https://vk.com/@profhak2019-master-klass-kto-takie-dzhuniory-midly-i-senory-i-zachem-ih 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/pydev-of-the-week.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: PyDev of the week 4 | language: ru 5 | date: 2021-09-13 6 | link: https://www.blog.pythonlibrary.org/2021/09/13/pydev-of-the-week-nikita-sobolev/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/python-quarantine-2020.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: workshop 3 | title: Python Quarantine 4 | language: ru 5 | date: 2020-04-06 6 | link: https://drylabs.io/py-quarantine 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/python-quarantine-second-wave-2020.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: workshop 3 | title: "Python Quarantine: Second Wave" 4 | language: ru 5 | date: 2020-12-01 6 | link: https://drylabs.io/py-quarantine2 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/rails-club-interview-elixir.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Rails Club 2017: I like Elixir in addition to Python" 4 | language: us 5 | date: 2017-09-21 6 | link: https://hype.codes/n-sobolev-i-elixir-addition-python 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/rails-club-interview-english.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Rails Club 2017: Intreview with Nikita Sobolev" 4 | language: us 5 | date: 2017-09-21 6 | link: https://hype.codes/n-sobolev-i-entered-elixir-about-2-months 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/rails-club-interview-video.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Rails Club 2017: Podcast with Nikita Sobolev" 4 | language: ru 5 | date: 2017-09-21 6 | link: https://www.youtube.com/watch?v=Ephx6uKNVa8 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/rails-club-interview.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Rails Club 2017: Interview with Nikita Sobolev" 4 | language: ru 5 | date: 2017-09-21 6 | link: https://habr.com/ru/company/railsclub/blog/338412/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/remote-vs-office.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Remote vs Office work" 4 | language: ru 5 | date: 2019-09-30 6 | link: https://habr.com/ru/post/469417/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/rpw-future-of-python.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Russian Python Week: Future of Python" 4 | language: us 5 | date: 2020-09-16 6 | link: https://www.youtube.com/watch?v=4AAFrRFiA8Y 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/rpw-what-is-python.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Russian Python Week: What is Python?" 4 | language: ru 5 | date: 2020-09-14 6 | link: https://www.youtube.com/watch?v=XA1UbxaCx7c 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/russian-python-week-2020-communities.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "RPW: Stream about our communities" 4 | language: ru 5 | date: 2020-08-31 6 | link: https://www.youtube.com/watch?v=23H-LU9FufQ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/russian-python-week-2020-formats1.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "RPW: Stream about our ideas: part one" 4 | language: ru 5 | date: 2020-08-13 6 | link: https://www.youtube.com/watch?v=8Ugny8evN60 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/russian-python-week-2020-formats2.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "RPW: Stream about our ideas: part two" 4 | language: ru 5 | date: 2020-08-19 6 | link: https://www.youtube.com/watch?v=-ynPxEN0RoU 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/russian-python-week-2020-stream.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "RPW: Stream about online conferences" 4 | language: ru 5 | date: 2020-08-06 6 | link: https://www.youtube.com/watch?v=dIDU1xu-Ujs 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/sdcast-about-python.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "sdcast: Python's place in this world" 4 | language: ru 5 | date: 2019-03-18 6 | link: https://sdcast.ksdaemon.ru/2019/03/sdcast-100/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/sdcast-about-rsdp.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "sdcast: about RSDP" 4 | language: ru 5 | date: 2019-06-28 6 | link: https://sdcast.ksdaemon.ru/2019/06/sdcast-105 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/searching-github-for-developers.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Webinar 3 | title: "Searching developers on Github" 4 | language: ru 5 | date: 2018-12-06 6 | link: https://moikrug.timepad.ru/event/859282/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/silver-bullet-podcast-rit.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "Silver bullet: from RIT++" 4 | language: ru 5 | date: 2019-05-27 6 | link: https://podcasts.google.com/?feed=aHR0cDovL2ZlZWRzLnNvdW5kY2xvdWQuY29tL3VzZXJzL3NvdW5kY2xvdWQ6dXNlcnM6NDgyODk5MzU5L3NvdW5kcy5yc3M&episode=dGFnOnNvdW5kY2xvdWQsMjAxMDp0cmFja3MvNjM2MDE1ODI1&at=1561386924256 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/smart-heads-podcast.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Podcast 3 | title: "SmartHead Space: about quality" 4 | language: ru 5 | date: 2021-04-28 6 | link: https://soundcloud.com/smarthead-space/pro-kachestvo-chast-1 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/tceh-python-communities.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Python communities" 4 | language: ru 5 | date: 2016-08-23 6 | link: https://tceh.com/post/komyuniti-programmistov/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/the-initial-commit-interview.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "_theInitialCommit with Nikita Sobolev" 4 | language: us 5 | date: 2017-09-05 6 | link: https://theinitialcommit.com/2017/09/05/nikita-sobolev/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/tproger-frontend-backend.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Frontenders vs Backenders" 4 | language: ru 5 | date: 2019-03-27 6 | link: https://tproger.ru/experts/frontend-and-backend/ 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/what-authorization-to-use-for-mobile-apps.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "What authorization to use for mobile app?" 4 | language: ru 5 | date: 2016-03-23 6 | link: https://vc.ru/ask/14408-problem-14711 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/why-learning-python.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "Why you should learn Python?" 4 | language: ru 5 | date: 2018-05-04 6 | link: https://vc.ru/promo/37415-zachem-izuchat-python 7 | --- 8 | -------------------------------------------------------------------------------- /_activities/you-dont-need-to-motivate-people-vc.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: Interview 3 | title: "You don't need to motivate people" 4 | language: ru 5 | date: 2019-01-31 6 | link: https://vc.ru/hr/57292-nikita-sobolev-u-nas-net-privychnoy-sistemy-motivacii-my-prosto-horosho-platim 7 | --- 8 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Site settings 2 | title: "sobolevn's personal blog" 3 | description: > 4 | On good software, mediocre software, and bad software. 5 | Also rants about management and development processes. 6 | 7 | author: Nikita Sobolev 8 | email: mail@sobolevn.me 9 | username: sobolevn 10 | 11 | baseurl: "" # the subpath of your site, e.g. /blog 12 | url: "https://sobolevn.me" # the base hostname & protocol for your site 13 | 14 | github_edit_url: "https://github.com/sobolevn/sobolevn.github.io/blob/master" 15 | disqus_id: sobolevn-me 16 | google_analytics_id: UA-66588818-1 17 | 18 | # Build settings 19 | include: [_pages] 20 | markdown: kramdown 21 | permalink: /:year/:month/:title 22 | 23 | collections: 24 | talks: 25 | output: true 26 | permalink: /:collection/:name 27 | activities: 28 | output: true 29 | permalink: /:collection:/:name 30 | 31 | feed: 32 | collections: 33 | - talks 34 | - activities 35 | 36 | sass: 37 | style: compressed 38 | 39 | exclude: [vendor] 40 | 41 | # Plugins: 42 | plugins: 43 | - jemoji 44 | - jekyll-sitemap 45 | - jekyll-feed 46 | -------------------------------------------------------------------------------- /_includes/analytics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /_includes/anchors.html: -------------------------------------------------------------------------------- 1 | {% capture headingsWorkspace %} 2 | {% comment %} 3 | Version 1.0.2 4 | https://github.com/allejo/jekyll-anchor-headings 5 | 6 | "Be the pull request you wish to see in the world." ~Ben Balter 7 | 8 | Usage: 9 | {% include anchor_headings.html html=content %} 10 | 11 | Parameters: 12 | * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll 13 | 14 | Optional Parameters: 15 | * beforeHeading (bool) : false - Set to true if the anchor should be placed _before_ the heading's content 16 | * anchorBody (string) : '' - The content that will be placed inside the anchor; the `%heading%` placeholder is available 17 | * anchorClass (string) : '' - The class(es) that will be used for each anchor. Separate multiple classes with a space 18 | * anchorTitle (string) : '' - The `title` attribute that will be used for anchors 19 | * h_min (int) : 1 - The minimum header level to build an anchor for; any header lower than this value will be ignored 20 | * h_max (int) : 6 - The maximum header level to build an anchor for; any header greater than this value will be ignored 21 | * bodyPrefix (string) : '' - Anything that should be inserted inside of the heading tag _before_ its anchor and content 22 | * bodySuffix (string) : '' - Anything that should be inserted inside of the heading tag _after_ its anchor and content 23 | 24 | Output: 25 | The original HTML with the addition of anchors inside of all of the h1-h6 headings. 26 | {% endcomment %} 27 | 28 | {% assign minHeader = include.h_min | default: 1 %} 29 | {% assign maxHeader = include.h_max | default: 6 %} 30 | {% assign beforeHeading = include.beforeHeading %} 31 | {% assign nodes = include.html | split: ' 6 %} 40 | {% capture edited_headings %}{{ edited_headings }}{{ node }}{% endcapture %} 41 | {% continue %} 42 | {% endif %} 43 | {% assign _workspace = node | split: '' | first }}>{% endcapture %} 48 | {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %} 49 | 50 | {% capture anchor %}{% endcapture %} 51 | {% if html_id and headerLevel >= minHeader and headerLevel <= maxHeader %} 52 | {% capture anchor %}href="#{{ html_id }}"{% endcapture %} 53 | {% if include.anchorClass %} 54 | {% capture anchor %}{{ anchor }} class="{{ include.anchorClass }}"{% endcapture %} 55 | {% endif %} 56 | {% if include.anchorTitle %} 57 | {% capture anchor %}{{ anchor }} title="{{ include.anchorTitle | replace: '%heading%', header }}"{% endcapture %} 58 | {% endif %} 59 | 60 | {% endif %} 61 | {% capture new_heading %} 62 | {{ header }} 65 | {{ include.bodySuffix }} 66 | 2 | 3 | 20 | 21 | -------------------------------------------------------------------------------- /_includes/favicons.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /_includes/footer.html: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% include favicons.html %} 8 | 9 | 10 | {% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %} 11 | 13 | 15 | 16 | 17 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 41 | 42 | -------------------------------------------------------------------------------- /_includes/header.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ site.title }}

3 | 4 | {% if page.title == nil %} 5 |

{{ site.description }}

6 | {% endif %} 7 | 8 | 33 |
34 | -------------------------------------------------------------------------------- /_includes/read_time.html: -------------------------------------------------------------------------------- 1 | 2 | {% assign words = include.post.content | number_of_words %} 3 | {% if words < 360 %} 4 | 1 min 5 | {% else %} 6 | {{ words | divided_by:180 }} mins 7 | {% endif %} read 8 | 9 | -------------------------------------------------------------------------------- /_layouts/content.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 |
5 |
6 |

{{ page.title }}

7 |
8 | 9 |
10 | {% include anchors.html html=content %} 11 |
12 | 13 | 14 |
15 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% include head.html %} 4 | 5 | 6 |
7 | {{ content }} 8 |
9 | 10 | {% include analytics.html %} 11 | 12 | 13 | -------------------------------------------------------------------------------- /_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 | {% include header.html %} 6 |
7 | {{ content }} 8 |
9 | {% include footer.html %} 10 |
11 | -------------------------------------------------------------------------------- /_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 |
6 |
7 |

{{ page.title }}

8 |

9 | 10 | 14 | 15 | {% include read_time.html post=page %} 16 | Start a discussion 17 | Edit this page 18 |

19 |
20 | 21 | 22 | 27 | 28 | 29 |
30 | 31 | {% if page.translated %} 32 | {% assign translations = page.translated.size %} 33 | {% if translations != 0 %} 34 |
35 |

Translated to:

36 | 48 |
49 | {% endif %} 50 | {% endif %} 51 | 52 |
53 | {% include anchors.html html=content %} 54 |
55 | 56 |
57 | 58 |
59 |

60 | 61 | Subscribe to my blog if you want more 62 | 63 |

64 |
65 | 66 | {% assign reposts = page.republished.size %} 67 | {% if reposts != 0 %} 68 |
69 |

Republished as:

70 | 80 |
81 | {% endif %} 82 |
83 | 84 | 85 | {% if page.comments != false and site.disqus_id %} 86 |
87 | {% include disqus.html %} 88 |
89 | {% endif %} 90 |
91 | -------------------------------------------------------------------------------- /_layouts/talk.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 |
6 |
7 |

{{ page.title }}

8 |

9 | 10 | 14 | 15 | {{ page.conference }} 16 | {{ page.location }} 17 | Edit this page 18 |

19 |
20 | 21 | 22 | 27 | 28 | 29 |
30 | {{ content }} 31 | 32 | {% if page.youtube %} 33 |

Video

34 | 39 | {% endif %} 40 | 41 | {% if page.slides %} 42 |

Slides

43 |

SpeakerDeck

44 | {% endif %} 45 |
46 | 47 | 48 | 71 |
72 | -------------------------------------------------------------------------------- /_pages/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: content 3 | title: About 4 | permalink: /about/ 5 | --- 6 | 7 | I am Nikita Sobolev (Russian: Никита Соболев). 8 | This is how I usually look like: 9 | 10 | My photo 11 | 12 | 13 | ## Work 14 | 15 | I work as a CTO at [wemake.services](https://wemake-services.github.io), 16 | I also happen to be a founder of this company. 17 | 18 | The thing I like about my job is freedom, I can: 19 | - Create new products for our clients 20 | - Write a lot of [open-source code](https://github.com/sobolevn) [:star:](https://stars.github.com/profiles/sobolevn/) 21 | - Work on processes in our company, that's exactly how I came up with [`RSDP`](https://wemake-services.github.io/meta/) 22 | - Spread a word about what I do with my [blog posts](https://sobolevn.me/) and [conference talks](https://sobolevn.me/talks/) 23 | - Participate in different closely-related [activities](https://sobolevn.me/activities/) 24 | 25 | ### Side projects 26 | 27 | I also run a few side projects: 28 | 29 | - [hrvs.tech](https://hrvs.tech/) where I help recruiters to write great vacancies for developers (**closed**) 30 | - [drylabs.io](https://drylabs.tilda.ws) where I consult people about their architecture and design decisions. This project is started with sustainable open-source model in mind 31 | - [OpensourceFindings](https://t.me/opensource_findings) channel where I share and advertise awesome open-source related projects, articles, and talks 32 | 33 | 34 | ## Stack 35 | 36 | I work with `python`, `elixir`, and `javascript` (`typescript` too). 37 | But, I am also familiar with *many* other languages. 38 | 39 | You can check my [`.dotfiles`](https://github.com/sobolevn/dotfiles) to get 40 | the exact the same setup as I have. 41 | 42 | 43 | ## Conferences I organize 44 | 45 | I am also helping to organize several meetups and conferences: 46 | 47 | - [ElixirLangMoscow](http://elixir-lang.moscow/) which I founded back in 2016 48 | - [MoscowPythonConf](http://conf.python.ru) where I am a Program Committee member 49 | - [RussianPythonWeek](https://conf.python.ru/moscow/2020) where I am a Program Committee leader 50 | 51 | 52 | ## Education career 53 | 54 | I also like to spread the limited knowledge I have. 55 | I gave lectures in several Universities in Russia: 56 | 57 | - [MosPolytech](https://mospolytech.ru/) in 2017 58 | - [RANEPA](https://www.ranepa.ru/) in 2018 59 | - [IFMO](http://www.ifmo.ru/) in 2019 60 | - [SPbU](https://spbu.ru/) in 2020/2021 61 | 62 | And I have also created several 63 | independent courses for `#tceh`, `iidf`, `devman`, and `Numa Moscow`. 64 | 65 | I have also participated in 66 | [Google Summer of Code 2020](https://www.djangoproject.com/weblog/2020/may/13/summer-of-code/) 67 | as a mentor for Django Software Foundation. 68 | 69 | 70 | ## Identity 71 | 72 | I don't have any social network accounts or any messengers. 73 | You can only find me on platforms that are listed below on this page. 74 | 75 | I am using [`keybase`](https://keybase.io/sobolevn) to identify 76 | my verified online accounts. 77 | 78 | My public `gpg` key is: [`C9F576BBC3A81B65BAF16635FF672D568AE3C73E`](https://keybase.io/sobolevn/pgp_keys.asc?fingerprint=c9f576bbc3a81b65baf16635ff672d568ae3c73e) 79 | 80 | 81 | ## Contact me 82 | 83 | Please, drop me a line if you want to: 84 | 85 | 1. Create a new product using [`RSDP`](https://wemake-services.github.io/meta) 86 | 2. [Audit](https://wemake-services.github.io/meta/rsdp/audits/) your code 87 | 3. Invite me to [speak](https://sobolevn.me/talks/) at your conference or meetup 88 | 4. Invite me to make a [private consulting session](https://dry-labs.github.io/) 89 | -------------------------------------------------------------------------------- /_pages/activities.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Activities 4 | permalink: /activities/ 5 | --- 6 | 7 |
8 |

9 | This is an incomplete list of different activities that I participate in. 10 |

11 |

12 | These are mostly interviews, webinars, and podcasts with me. 13 |

14 |

15 | Do you want to invite me to make a podcast together? 16 | No problem, drop me a line! 17 |

18 |
19 | 20 |
21 | 22 | {% assign sorted_activities = site.activities | reverse %} 23 | {% for activity in sorted_activities %} 24 | {% capture this_year %}{{ activity.date | date: "%Y" }}{% endcapture %} 25 | {% capture next_year %}{{ activity.previous.date | date: "%Y" }}{% endcapture %} 26 | 27 | {% if forloop.first %} 28 |

{{ this_year }}

29 | 45 | {% else %} 46 | {% if this_year != next_year %} 47 | 48 | 49 |

50 | {{ next_year }} 51 |

52 |
57 | -------------------------------------------------------------------------------- /_pages/subscribe.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: content 3 | title: Subscribe 4 | permalink: /subscribe/ 5 | --- 6 | 7 | ## RSS 8 | 9 | You can subscribe to my blog posts with [`RSS`](https://sobolevn.me/feed.xml). 10 | You can also subscribe to my [Activities](https://sobolevn.me/feed/activities.xml) and [Talks](https://sobolevn.me/feed/talks.xml) feeds as well. 11 | 12 | If you don't know what `RSS` is, then it is not your choice. 13 | Consider Github subscription. 14 | 15 | 16 | ## Telegram 17 | 18 | I cross-post all articles and tools I make in [`@OpensourceFindings`](https://t.me/opensource_findings) telegram channel. 19 | You can join it if you are using this platform: you will also receive lots of extra open-source goodies made by other developers. 20 | 21 | 22 | ## Github 23 | 24 | You can also [follow me on Github](https://github.com/sobolevn) 25 | and be notified about everything I do, no just blog posts. 26 | 27 | Or you can watch only [blog repository](https://github.com/sobolevn/sobolevn.github.io) 28 | for new changes. 29 | -------------------------------------------------------------------------------- /_pages/talks.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Talks 4 | permalink: /talks/ 5 | future_talks: [] 6 | --- 7 | 8 |
9 |

10 | I love conference and meetups. I generally speak about python, 11 | elixir, javascript, and management. 12 |

13 |

14 | You can invite me to speak at your event. 15 | Just drop me a line! 16 |

17 | 18 |

I gave {{ site.talks.size }} public talks total.

19 |
20 | 21 |
22 | 23 | 24 | {% if page.future_talks %} 25 |

Upcoming

26 | 27 | 42 | 43 | {% endif %} 44 | 45 | {% assign sorted_talks = site.talks | reverse %} 46 | {% for talk in sorted_talks %} 47 | {% capture this_year %}{{ talk.date | date: "%Y" }}{% endcapture %} 48 | {% capture next_year %}{{ talk.previous.date | date: "%Y" }}{% endcapture %} 49 | 50 | {% if forloop.first %} 51 |

{{ this_year }}

52 | 67 | {% else %} 68 | {% if this_year != next_year %} 69 | 70 | 71 |

72 | {{ next_year }} 73 |

74 |
79 | -------------------------------------------------------------------------------- /_posts/2017-04-01-managing-djangos-settings.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Managing Django’s settings" 4 | description: "Managing Django’s settings might be tricky. There are severals issues which are encountered by any Django developer along the way." 5 | date: 2017-04-01 6 | tags: python django 7 | republished: 8 | - link: https://medium.com/wemake-services/managing-djangos-settings-e2b7f496120d 9 | language: us 10 | - link: https://dev.to/wemake-services/managing-djangos-settings-37ao 11 | language: us 12 | --- 13 | 14 | ![django-split-settings logo](https://cdn-images-1.medium.com/max/1600/1*ogBWTa2xptrsZXMB34Pzgw.png) 15 | 16 | Managing Django’s settings might be tricky. There are severals issues which are encountered by any Django developer along the way. 17 | 18 | First one is caused by the default project structure. Django clearly offers us a single `settings.py` file. It seams reasonable at the first glance. And it is actually easy to use just after the start. But when it comes to the real-world it only causes misunderstanding and frustration. 19 | 20 | At some point, you will need to put some kind of personal settings in the main file: certificate paths, your username or password, database connection, etc. But putting your user-specific values inside the common settings is a bad practice. Other developers would have other settings, and it would just not work for all of you. The most known hack for this situation is `local_settings.py`. This file is placed near the regular settings file and ignored from version control. There are also these lines which are usually put somewhere in the end of `settings.py`: 21 | 22 | ```python 23 | try: 24 | from local_settings import * 25 | except ImportError: 26 | # No local settings was found, skipping. 27 | pass 28 | ``` 29 | 30 | Looks pretty straight-forward. It is also sometimes accompanied with `local_settings.py.template`, which is version controlled, to keep your local settings structure up to date. 31 | 32 | You would definitely need production settings sometime soon. How would you do that? Create a new file. Do you need special settings for testing? Create a new file. Staging? New file. *You would have a lot of files.* 33 | 34 | Secondly, when you have a lot of things to configure, your settings files will become long and heavy. At this point you would start to think: maybe I could separate these values into different files and reuse them at different environments? If this thought has ever come to your mind — you should give [django-split-settings](https://github.com/sobolevn/django-split-settings) a try. 35 | 36 | ## Usage 37 | 38 | How does `django-split-settings` solve these issues? This helper provides a user-friendly interface to store your settings in different files. Let’s look at the example. Imagine you have an existing project with `django`, `postgres`, `redis`, `rq`, and emails. 39 | 40 | Before we start, let’s install `django-split-settings` with: 41 | 42 | ```bash 43 | pip install django-split-settings 44 | ``` 45 | 46 | That’s what your files would look like after adopting `django-split-settings`: 47 | 48 | ```bash 49 | your_project/settings/ 50 | ├── __init__.py 51 | ├── components 52 | │ ├── __init__.py 53 | │ ├── database.py 54 | │ ├── common.py 55 | │ ├── emails.py 56 | │ ├── rq.py 57 | └── environments 58 | ├── __init__.py 59 | ├── development.py 60 | ├── local.py.template 61 | ├── production.py 62 | └── testing.py 63 | ``` 64 | 65 | That’s a clear separation of the settings based on two factors: what component they are configuring and at what environment we are working right now. And the flexibility of the library allows you to have any structure you want, not just the one described here. 66 | 67 | In our `settings/__init__.py` we can define any logic we want. Basically, we would just define what kind of components we would like to use and select the environment. Here’s an example, we use in production for all our projects: 68 | 69 | ```python 70 | """ 71 | This is a django-split-settings main file. 72 | For more information read this: 73 | https://github.com/sobolevn/django-split-settings 74 | Default environment is `developement`. 75 | To change settings file: 76 | `DJANGO_ENV=production python manage.py runserver` 77 | """ 78 | 79 | from split_settings.tools import optional, include 80 | from os import environ 81 | 82 | ENV = environ.get('DJANGO_ENV') or 'development' 83 | 84 | base_settings = [ 85 | 'components/common.py', # standard django settings 86 | 'components/database.py', # postgres 87 | 'components/rq.py', # redis and redis-queue 88 | 'components/emails.py', # smtp 89 | 90 | # You can even use glob: 91 | # 'components/*.py' 92 | 93 | # Select the right env: 94 | 'environments/{0}.py'.format(ENV), 95 | # Optionally override some settings: 96 | optional('environments/local.py'), 97 | ] 98 | 99 | # Include settings: 100 | include(*base_settings) 101 | ``` 102 | 103 | And that’s it. Our application would run as usual. We have achieved multiple goals with so few lines of code: 104 | 105 | 1. We now have separated settings based on what they configure. Gaining readability and maintainability 106 | 2. We now have separated settings based on environment 107 | 3. We now have optional local settings with now dirty hacks 108 | 4. We did not have to do any refactoring except just some basic restructuring 109 | 110 | We have also created a project example, which can be used as a template for your own projects: [https://github.com/wemake-services/wemake-django-template](https://github.com/wemake-services/wemake-django-template) 111 | 112 | ## What’s not covered 113 | 114 | In a future articles we would cover two topics which are crucial when dealing with project’s configuration: 115 | 116 | 1. Secret settings 117 | 2. Dynamic settings 118 | 119 | ## Afterword 120 | 121 | Got any ideas or feedback? Dive in, if you want to contribute: 122 | [django-split-settings - Organize Django settings into multiple files and directories.](https://github.com/sobolevn/django-split-settings) 123 | -------------------------------------------------------------------------------- /_posts/2017-06-12-is-yarn-still-a-thing.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Is Yarn still a thing?" 4 | description: "When npm@5 was just released this question was the first one I have googled. No doubts it comes to mind since new npm version introduced a lot of yarn’s features. In other words: should I still use yarn after installing npm@5?" 5 | date: 2017-06-12 6 | tags: javascript 7 | republished: 8 | - link: https://medium.com/wemake-services/is-yarn-still-a-thing-3c6886410c83 9 | language: us 10 | --- 11 | 12 | When `npm@5` was just released this question was the first one I have googled. 13 | No doubts it comes to mind since new npm version introduced a lot of yarn’s features. 14 | In other words: should I still use yarn after installing npm@5? 15 | 16 | ### Yarn features 17 | 18 | Why do people use yarn in the first place? `npm` had some known issues. 19 | Well, we all have been there: downloading and installing packages for hours, resolution hell, not using essential `--save` and `--save-dev` by default, and many others. 20 | 21 | When yarn was [first released](https://code.facebook.com/posts/1840075619545360) it solved many of these issues completely. It offered multiple improvements: 22 | 23 | 1. `yarn add` saves a package not only to `node_modules` but also adds it to the list of dependencies in `package.json`. Think of it like `yarn` does not install a package into `node_modules` directory, it [adds a package](https://yarnpkg.com/en/docs/managing-dependencies) to your project 24 | 25 | 2. `yarn install` worked in average from 2 to 3 times faster than `npm install`. `yarn` changes how packages are downloaded and installed, that’s why it is so blazingly fast 26 | 27 | 3. `yarn install` also checks for `yarn.lock` (or creates it), a special file where every single version is locked into a known state, what makes dependency resolution process deterministic 28 | 29 | 4. `yarn` utilizes cache to make the installation process even faster. It is even possible to reinstall everything without internet connection when the cache is alive (saved me once) 30 | 31 | This set of advantages at some point predetermined how the js package manager should look like. `npm` had to take the pace. 32 | 33 | ### npm@5 breaks in 34 | 35 | Keeping all that in mind the npm core team made a huge step towards the competitor. When the 5th major release was out a lot of people asked this question: should we still use yarn? The [changelog](http://blog.npmjs.org/post/161081169345/v500) for this release is inspiring indeed. 36 | 37 | What are the key features that npm@5 brings to us? 38 | 39 | **Speed up:** it is now competing with yarn and other package managers. 40 | [Here’s a nice gif of the speed up](https://twitter.com/maybekatz/status/855362606713851904), brought to you by one of the `npm`'s core members. 41 | 42 | 43 | **Determinism**: npm now enforces the same workflow as `yarn` (and many other package managers). It generates `package-lock.json` to know what exact versions your project uses. It is worth mentioning that algorithms in `yarn` and `npm` differ. And `npm` has a solid advantage since it [has better hoisting position across npm versions](https://yarnpkg.com/blog/2017/05/31/determinism/) than `yarn` has across different version of `yarn`. 44 | 45 | **Sane defaults**: `--save` is now enabled by default. No more problems with that. 46 | 47 | **Cache**: it was completely [rewritten](https://github.com/npm/npm/pull/15666). `cacache` and `pacote` living inside the new realization are fast and reliable. You can run this command to see it yourself: 48 | 49 | ```bash 50 | git clone https://github.com/zkat/cacache && cd cacache && npm i && npm run benchmarks 51 | ``` 52 | 53 | **Default tool**: `npm` is the default. Everyone uses it. Earlier it was like `IE`: a browser to download another browser. Jokes aside, this point is strong. You don’t need to have this one extra custom package manager. 54 | 55 | ### But, really, is yarn still a thing? 56 | 57 | The answer is: it depends. 58 | 59 | My first attempt to install something with npm@5 was with my the most favorite [wemake-vue-template](https://github.com/wemake-services/wemake-vue-template) which has around 850 packages to download. `npm`'s time was not bad at all with 42 seconds at the fresh run. When the cache is ready, it takes only 30 seconds to install everything. 60 | 61 | Compared to `yarn`: 35 seconds without cache and 20 seconds with the cache in place. For me, this time gap was important enough to still use `yarn` as a primary tool. 62 | 63 | But. **Do not use both tools inside one team**. It will lead to a disaster with package resolution and pollute your repository with extra files. Stick to something and use it. 64 | 65 | ### Finale 66 | 67 | `npm` is moving in a right direction (say hi to pip). 68 | It is pretty great already, but soon it will be even cooler. 69 | -------------------------------------------------------------------------------- /_posts/2017-07-19-creating-slugs-for-ecto-schemas.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Creating slugs for Ecto schemas" 4 | description: "The term “slug” comes from the world of newspaper production. If you have ever created a simple “Blog” application you have already reinvented it. Let's have a look at how you can use slugs in Elixir apps." 5 | date: 2017-07-19 6 | tags: elixir 7 | republished: 8 | - link: https://medium.com/wemake-services/creating-slugs-for-ecto-schemas-7349513410f0 9 | language: us 10 | --- 11 | 12 | ## What is a slug? 13 | 14 | The term “slug” comes from the world of newspaper production. If you have ever created a simple “Blog” application you have already reinvented it. 15 | 16 | When you need to access any post at some URL you need to identify it somehow. The simplest idea is to identify them by id, but that does not seem too pretty. 17 | It would be better to identify your posts based on its title or content. 18 | So, a blog post with a title “Creating slugs for Ecto models” would be accessible via a nice-looking URL: `myapp.com/posts/creating-slugs-for-ecto-models`. Where `creating-slugs-for-ecto-models` is a slug. Pretty cool, isn’t it? 19 | 20 | > So, a slug is a good-looking web-safe unique string used to identify some data. 21 | 22 | Creating slugs is so common, that we have actually created a utility library to generate slugs based on some other fields. 23 | We called it [ecto_autoslug_field](https://github.com/sobolevn/ecto_autoslug_field). 24 | 25 | ## How does EctoAutoslugField work? 26 | 27 | Imagine that you have a simple Article schema inside Blog context. 28 | That’s how it looks like freshly out of the generator: 29 | 30 | ```elixir 31 | defmodule EctoSlugs.Blog.Article do 32 | use Ecto.Schema 33 | import Ecto.Changeset 34 | alias EctoSlugs.Blog.Article 35 | 36 | schema "blog_articles" do 37 | field :breaking, :boolean, default: false 38 | field :content, :string 39 | field :title, :string 40 | 41 | timestamps() 42 | end 43 | 44 | @doc false 45 | def changeset(%Article{} = article, attrs) do 46 | article 47 | |> cast(attrs, [:title, :content, :breaking]) 48 | |> validate_required([:title, :content]) 49 | |> unique_constraint(:title) 50 | end 51 | end 52 | ``` 53 | 54 | It has just three meaning-full fields: `:title` which is unique, `:content`, and if this article has some `:breaking` news in it which is set to false by default. 55 | 56 | Now you want to generate a slug from the title. 57 | 58 | ### Installation 59 | 60 | Firstly, add `{:ecto_autoslug_field, “~> 0.3”}` to your `mix.exs`. 61 | Then fetch the dependencies with mix deps.get. 62 | 63 | Then you will need to add a new field to your schema. Let’s call it `:slug`. 64 | 65 | ```elixir 66 | defmodule EctoSlugs.Blog.Article do 67 | use Ecto.Schema 68 | import Ecto.Changeset 69 | alias EctoSlugs.Blog.Article 70 | alias EctoSlugs.Blog.Article.TitleSlug 71 | 72 | schema "blog_articles" do 73 | field :breaking, :boolean, default: false 74 | field :content, :string 75 | field :title, :string 76 | 77 | field :slug, TitleSlug.Type 78 | 79 | timestamps() 80 | end 81 | 82 | @doc false 83 | def changeset(%Article{} = article, attrs) do 84 | article 85 | |> cast(attrs, [:title, :content, :breaking]) 86 | |> validate_required([:title, :content]) 87 | |> unique_constraint(:title) 88 | |> TitleSlug.maybe_generate_slug 89 | |> TitleSlug.unique_constraint 90 | end 91 | end 92 | ``` 93 | 94 | Take a close note on this `TitleSlug` used everywhere. 95 | That would be a slug field module. But right now it does not exist. 96 | We will implement it later. 97 | 98 | You will also need to add a new migration. It would be something like this: 99 | 100 | ```elixir 101 | defmodule EctoSlugs.Repo.Migrations.CreateEctoSlugs.Blog.Article do 102 | use Ecto.Migration 103 | 104 | def change do 105 | alter table(:blog_articles) do 106 | add :slug, :string 107 | end 108 | 109 | create unique_index(:blog_articles, [:slug]) # should be unique 110 | end 111 | end 112 | ``` 113 | 114 | ### Simple example 115 | 116 | Remember, that we did not implement `TitleSlug` yet? Let’s decide what it should look like. The first use case is the simplest one. When an article is created, generate a slug from it’s title. Do nothing more. 117 | 118 | ```elixir 119 | defmodule EctoSlugs.Blog.Article.TitleSlug do 120 | use EctoAutoslugField.Slug, from: :title, to: :slug 121 | end 122 | ``` 123 | 124 | That’s it. It takes a value from `:from` field and puts changes into `:to` field. But maybe that’s not what you want? Let’s step it up. 125 | 126 | ### Conditional example 127 | 128 | Imagine that you have a business rule: put “breaking” in front of every breaking news article’s slug. How could you achieve that? 129 | 130 | ```elixir 131 | defmodule EctoSlugs.Blog.Article.TitleSlug do 132 | use EctoAutoslugField.Slug, to: :slug 133 | 134 | import Ecto.Changeset 135 | 136 | def get_sources(changeset, _opts) do 137 | # This function is used to get sources to build slug from: 138 | base_fields = [:title] 139 | 140 | if get_change(changeset, :breaking, false) do 141 | ["breaking"] ++ base_fields 142 | else 143 | base_fields 144 | end 145 | end 146 | end 147 | ``` 148 | 149 | Note, that right now we don’t use :from option anymore, instead, we are using a `get_sources/2` function. 150 | What does it do? When creating an article it will provide conditional sources for the slug. 151 | And since it has access to the changeset struct the possibilities are endless. 152 | The logic itself is also pretty simple. 153 | When changeset has `:breaking` key set to true prepend “breaking” to the sources list. 154 | Your business rule is now satisfied, let’s move on. 155 | 156 | ### Modify the slug 157 | 158 | Or maybe you have a different use case? 159 | The business wants your slugs to be joined differently. 160 | So you need to modify the resulting slug. How to do that? 161 | 162 | ```elixir 163 | defmodule EctoSlugs.Blog.Article.TitleSlug do 164 | use EctoAutoslugField.Slug, from: :title, to: :slug 165 | 166 | def build_slug(sources, _changeset) do 167 | sources 168 | |> super() 169 | |> String.replace("-", "+") 170 | end 171 | end 172 | ``` 173 | 174 | It is possible to define a custom `build_slug/2` function which accepts two arguments: the list of sources and the initial changeset. 175 | This function is designed to build and return the slug before it is saved to the database. 176 | This `super()` call transforms your list of sources into the slug-string. 177 | 178 | But before the slug is returned you can do multiple things: 179 | 180 | * check either your slug is unique, if not — increment it somehow 181 | * modify your slug 182 | * or even build the slug yourself without this magic `super()` call 183 | 184 | And of course, you can use this function alongside the `get_sources/2`. 185 | 186 | ## Conclusion 187 | 188 | That’s a short introduction to `ecto_autoslug_field`. 189 | 190 | But that’s not even all its features covered! There are more options and possibilities. 191 | Like, recreating slug on every save with `:always_change` option and others. 192 | We have tried to cover everything inside documentation and it is [available online](https://hexdocs.pm/ecto_autoslug_field/readme.html). 193 | 194 | Check `ecto_autoslug_field` out: 195 | [ecto_autoslug_field - Automatically create slugs for Ecto schemas.](https://github.com/sobolevn/ecto_autoslug_field) 196 | -------------------------------------------------------------------------------- /_posts/2017-08-23-instant-command-line-productivity.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Instant +100% command line productivity boost 4 | description: Brief overview of some awesome command line utilities to increase your productivity and make you a happier developer 5 | date: 2017-08-23 6 | tags: bash python 7 | republished: 8 | - link: https://dev.to/sobolevn/testing-bash-scripts 9 | language: us 10 | --- 11 | 12 | Being productive is fun. 13 | 14 | There are a lot of fields to improve your productivity. Today I am going to share some command line tips and tricks to make your life easier. 15 | 16 | 17 | ## TLDR 18 | 19 | My full setup includes all the stuff discussed in this article and even more. 20 | Check it out: [https://github.com/sobolevn/dotfiles](https://github.com/sobolevn/dotfiles) 21 | 22 | 23 | ## Shell 24 | 25 | Using a good, helping, and the stable shell is the key to your command line productivity. While there are many choices, I prefer `zsh` coupled with `oh-my-zsh`. It is amazing for several reasons: 26 | 27 | - Autocomplete nearly everything 28 | - Tons of plugins 29 | - Really helping and customizable `PROMPT` 30 | 31 | You can follow these steps to install this setup: 32 | 33 | 1. Install [`zsh`](https://github.com/robbyrussell/oh-my-zsh/wiki/Installing-ZSH) 34 | 2. Install [`oh-my-zsh`](http://ohmyz.sh/) 35 | 3. Choose [plugins](https://github.com/robbyrussell/oh-my-zsh/wiki/Plugins) that might be useful for you 36 | 37 | You may also want to tweak your settings to [turn off case sensitive autocomplete](https://github.com/sobolevn/dotfiles/blob/master/config/zshrc#L12). Or change how your [history works](https://github.com/sobolevn/dotfiles/blob/master/config/zshrc#L24). 38 | 39 | That's it. You will gain instant +50% productivity. Now hit tab as much as you can! 40 | 41 | 42 | ## Theme 43 | 44 | Choosing theme is quite important as well since you see it all the time. It has to be functional and pretty. I also prefer minimalistic themes, since it does not contain a lot of visual noise and unused information. 45 | 46 | Your theme should show you: 47 | 48 | - current folder 49 | - current branch 50 | - current repository status: clean or dirty 51 | - error codes if any 52 | 53 | I also prefer my theme to have new commands on a new line, so there is enough space to read and write it. 54 | 55 | I personally use [`sobole`](https://github.com/sobolevn/sobole-zsh-theme). It looks pretty awesome. It has two modes. 56 | 57 | Light: 58 | 59 | ![sobole.zsh-theme](https://raw.githubusercontent.com/sobolevn/sobole-zsh-theme/master/showcases/env-and-user.png) 60 | 61 | And dark: 62 | 63 | ![sobole.zsh-theme](https://raw.githubusercontent.com/sobolevn/sobole-zsh-theme/master/showcases/ls-colors-dark.png) 64 | 65 | Get your another +15% boost. And an awesome-looking theme. 66 | 67 | 68 | ## Syntax highlighting 69 | 70 | For me, it is very important to have enough visual information from my shell to make right decisions. Like "does this command have any typos in its name" or "do I have paired scopes in this command"? And I really make tpyos all the time. 71 | 72 | So, [`zsh-syntax-highlighting`](https://github.com/zsh-users/zsh-syntax-highlighting) was a big finding for me. It comes with reasonable defaults, but you can [change anything you want](https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/docs/highlighters.md). 73 | 74 | These steps brings us extra +5%. 75 | 76 | 77 | ## Working with files 78 | 79 | I travel inside my directories a lot. Like *a lot*. And I do all the things there: 80 | 81 | - navigating back and forwards 82 | - listing files and directories 83 | - printing files' contents 84 | 85 | I prefer to use [`z`](https://github.com/rupa/z) to navigate to the folders I have already been to. This tool is awesome. It uses 'frecency' method to turn your `.dot TAB` into `~/dev/shell/config/.dotfiles`. Really nice! 86 | 87 | When printing files you want usually to know several things: 88 | 89 | - file names 90 | - permissions 91 | - owner 92 | - git status of the file 93 | - modified date 94 | - size in human readable form 95 | 96 | You also probably what to show hidden files to show up by default as well. So, I use [`exa`](https://github.com/ogham/exa) as the replacement for standard `ls`. Why? Because it has a lot of stuff enabled by default: 97 | 98 | ![exa](https://raw.githubusercontent.com/ogham/exa/master/screenshots.png) 99 | 100 | To print the file contents I use standard `cat` or if I want to see to proper syntax highlighting I use a custom alias: 101 | 102 | ``` 103 | # exa: 104 | alias la="exa -abghl --git --color=automatic" 105 | 106 | # `cat` with beautiful colors. requires: pip install -U Pygments 107 | alias c='pygmentize -O style=borland -f console256 -g' 108 | ``` 109 | 110 | Now you have mastered the navigation. Get your +15% productivity boost. 111 | 112 | 113 | ## Searching 114 | 115 | When searching in a source code of your applications you don't want to include folders like `node_modules` or `bower_components` into your results by default. You also want your search to be fast and smooth. 116 | 117 | Here's a good replacement for the built in search methods: [`the_silver_searcher`](https://github.com/ggreer/the_silver_searcher). 118 | 119 | It is written in pure `C` and uses a lot of smart logic to work fast. 120 | 121 | Using `ctrl` + `R` for [reverse search](https://unix.stackexchange.com/questions/73498/how-to-cycle-through-reverse-i-search-in-bash) in `history` is very useful. But have you ever found yourself in a situation when I can quite remember a command? What if there were a tool that makes this search even greater enabling fuzzy searching and a nice UI? 122 | 123 | There is such a tool, actually. It is called `fzf`: 124 | 125 | ![fzf](https://thepracticaldev.s3.amazonaws.com/i/erts5tffgo5i0rpi8q3r.png) 126 | 127 | It can be used to fuzzy-find anything, not just history. But it requires [some configuration](https://github.com/sobolevn/dotfiles/blob/master/shell/.external#L19). 128 | 129 | You are now a search ninja with +15% productivity bonus. 130 | 131 | 132 | ## Conclusion 133 | 134 | Following these simple steps, you can dramatically increase your command line productivity, like +100% (numbers are approximate). 135 | 136 | There are other tools and hacks I will cover in the next articles. 137 | 138 | 139 | ## Further reading 140 | 141 | [Using better CLIs](https://dev.to/sobolevn/using-better-clis-6o8) 142 | -------------------------------------------------------------------------------- /_posts/2017-10-09-using-better-clis.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Using better CLIs 4 | description: Don't stick to the default CLIs, there are better alternatives! 5 | date: 2017-10-09 6 | tags: bash python 7 | republished: 8 | - link: https://dev.to/sobolevn/using-better-clis-6o8 9 | language: us 10 | --- 11 | 12 | For people who spend half of their lives in a terminal user experience and functionality is highly important. Making you a happier person. 13 | 14 | Here are some very good alternatives to some default command line applications. 15 | 16 | 17 | ## TLDR 18 | 19 | My full setup includes all the stuff discussed in this article and even more. 20 | Check it out: [https://github.com/sobolevn/dotfiles](https://github.com/sobolevn/dotfiles) 21 | 22 | 23 | ## Git 24 | 25 | ### hub 26 | 27 | When working with open-source (and Github) projects frequently sometimes `git` is not enough. So, Github created a tool called [`hub`](https://hub.github.com/). 28 | 29 | It allows to fetch from remote forks, browse issues, and create pull requests with ease! 30 | 31 | ``` 32 | # Open the current project's issues page 33 | $ git browse -- issues 34 | → open https://github.com/github/hub/issues 35 | 36 | # Fetch from multiple forks, even if they don't yet exist as remotes: 37 | $ git fetch user1,fork2 38 | 39 | # Browse issues: 40 | $ git browse -- issues 41 | → open https://github.com/github/hub/issues 42 | 43 | # Create a pull request: 44 | $ git pull-request -F message-template.md 45 | ``` 46 | 47 | ### tig 48 | 49 | Let's face it. `git log` sucks. It allows browsing commits history, but when you want to look inside a specific commit for its changesets or tree structure, well ... You will have to memorize all these commands or use a lot of external plugins. 50 | 51 | `tig` solves it all. Firstly, it allows to browse commits history. Then you can dive inside! Browse changesets, file trees, blames, and even blobs! 52 | 53 | ![tig](https://i.imgur.com/0EVZxQb.png) 54 | 55 | 56 | ## Utils 57 | 58 | ### postgres (and mysql too!) 59 | 60 | When working with `postgres` we have to use `psql`. And it is quite good. It has history, some basic autocomplete and commands that are easy to remember. But, there is a better tool called [`pgcli`](https://github.com/dbcli/pgcli). 61 | 62 | ![pgcli](https://raw.githubusercontent.com/dbcli/pgcli/7c720a07652d705376af6bf4fcfe6a65e0df3ddc/screenshots/pgcli.gif) 63 | 64 | Features: 65 | - smart autocomplete 66 | - syntax highlighting 67 | - pretty prints of tabular data 68 | 69 | It also has a version for `mysql` called [`mycli`](https://github.com/dbcli/mycli). 70 | 71 | By the way, yesterday a new [10th version](https://www.postgresql.org/about/news/1786/) of `postgres` was released. 72 | 73 | ### glances 74 | 75 | System monitoring is a common task for every developer. Standard tools like `top` and `htop` are fine and trusted software. But look at this beauty, [`glances`](https://github.com/nicolargo/glances): 76 | 77 | ![glances](https://raw.githubusercontent.com/nicolargo/glances/develop/docs/_static/glances-summary.png) 78 | 79 | `glances` has a lot of plugins to monitor almost everything: https://github.com/nicolargo/glances#requirements 80 | 81 | It also has a web interface and a pre-build `docker`-container to integrate it easily. My top list of plugins: 82 | - `docker` 83 | - `gpu` (very useful for miners and coins-folks!) 84 | - `bottle` (web-interface) 85 | - `netifaces` (IPs) 86 | 87 | Create [your own](https://github.com/nicolargo/glances/wiki/How-to-create-a-new-Glances-plugin-%3F) if you want to! 88 | 89 | ## httpie 90 | 91 | `curl` and `wget` are well-known and widely used. But are they user-friendly? I don't think so. `httpie` **is** user-friendly and can do everything these tools can: 92 | 93 | ![httpie](https://httpie.org/static/img/httpie2.png?v=72661be530fde9d07e03be9df60312da) 94 | 95 | And even [more](https://httpie.org/doc#main-features). I don't regret a single minute using it instead of `curl`. 96 | 97 | ### jq 98 | 99 | [`jq`](https://stedolan.github.io/jq/manual/) is like `sed` for `json`. It is useful in automation, configuration reading, and making requests. 100 | 101 | You can try it [online](https://jqplay.org/). 102 | 103 | ### doitlive 104 | 105 | Sometimes you have to do something live: a screencast, a gif, a talk. But everything can go wrong. You can make a typo, or misspell a word. That's where [`doitlive`](https://github.com/sloria/doitlive) comes to the rescue. 106 | 107 | Just create a file called `session.sh` with command that needs to be executed and then run: 108 | 109 | ``` 110 | doitlive play session.sh 111 | ``` 112 | 113 | Now you are a command line magician. 114 | 115 | 116 | ## Python 117 | 118 | I do a lot of `python` development. So, here are my tools to make it better. 119 | 120 | ### pipsi 121 | 122 | [`pipsi`](https://github.com/mitsuhiko/pipsi) = `pip` Script Installer. It creates a `virtualenv` for every script and symlinks it into your `/usr/local/bin`. So it won't pollute your global environment. 123 | 124 | ### pipenv 125 | 126 | [`pipenv`](http://pipenv.org) is a tool that aims to bring the best of all packaging worlds (bundler, composer, npm, cargo, yarn, etc.) to the Python world. 127 | 128 | ![pipenv](https://camo.githubusercontent.com/2287c881cb3a045f4f70f20f0326ec4ef1474ccd/687474703a2f2f6d656469612e6b656e6e657468726569747a2e636f6d2e73332e616d617a6f6e6177732e636f6d2f706970656e762e676966) 129 | 130 | The problems that Pipenv seeks to solve are multi-faceted: 131 | 132 | - You no longer need to use pip and virtualenv separately. They work together. 133 | - Managing a requirements.txt file can be problematic, so Pipenv uses the upcoming Pipfile and Pipfile.lock instead, which is superior for basic use cases. 134 | - Hashes are used everywhere, always. Security. Automatically expose security vulnerabilities. 135 | - Give you insight into your dependency graph (e.g. `$ pipenv graph`). 136 | - Streamline development workflow by loading `.env` files. 137 | 138 | ### ipython 139 | 140 | [`ipython`](https://ipython.org/) = Interactive `python`. 141 | 142 | `ipython` brings autocomplete, nice history, and multiline editing to the `python` shell. It integrates into `django` and `flask` nicely without any configuration. 143 | It is a must for all of my projects. If you like it, also check out [`jupyter`](https://jupyter.org/). 144 | 145 | 146 | ## Previous series 147 | 148 | - [Instant 100% command line productivity boost](https://sobolevn.me/2017/08/instant-command-line-productivity) 149 | -------------------------------------------------------------------------------- /_posts/2018-10-21-real-python-contants.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 1-minute guide to real constants in Python 4 | description: How to use final types to make your API better 5 | date: 2018-07-08 6 | tags: python 7 | republished: 8 | - link: https://dev.to/wemake-services/1-minute-guide-to-real-constants-in-python-2bpk 9 | language: us 10 | --- 11 | 12 | 13 | 14 | Many languages like `java` and `php` share a concept of `final` entities. 15 | `final` entity is something that can not be changed. 16 | 17 | We did not have this feature in `python`. Until two events happened recently: 18 | 19 | 1. I have released [`final-class` package](https://github.com/moscow-python-beer/final-class) 20 | 2. `python` core team [has released](https://github.com/python/mypy/pull/5522) official `final` support in `typing` module 21 | 22 | Now we truly have a new *shiny* language feature! Let's dig into how it works and why it's so awesome. 23 | 24 | ## Declaring constants 25 | 26 | First of all, you will need to install `mypy` and `type_extensions`: 27 | 28 | ```bash 29 | » pip install mypy typing_extensions 30 | ``` 31 | 32 | Then we can start to use it: 33 | 34 | ```python 35 | from typing_extensions import Final 36 | 37 | DAYS_IN_A_WEEK: Final = 7 38 | ``` 39 | 40 | That's it! But, what will happen if we try to modify this constant? 41 | 42 | 43 | ```python 44 | from typing_extensions import Final 45 | 46 | DAYS_IN_A_WEEK: Final = 7 47 | DAYS_IN_A_WEEK = 8 # I really want more days in a week! 48 | ``` 49 | 50 | Really, nothing. This is just good old `python` where you can do bizarre things with no payback. It just does not care about type annotation. 51 | 52 | All the magic happens only when we run `mypy` type checker: 53 | 54 | ``` 55 | » mypy --python-version=3.6 --strict week.py 56 | week.py:4: error: Cannot assign to final name "DAYS_IN_A_WEEK" 57 | ``` 58 | 59 | Boom! We have a constant here! 60 | 61 | See how `Final` type deals with underlying types. You don't have to manually tell the type checker what the type actually is. It will [figure it out](https://en.wikipedia.org/wiki/Type_inference) all by itself. In other words, type checker will know that `DAYS_IN_A_WEEK` is `int`. 62 | 63 | ## Interfaces 64 | 65 | And it goes beyond just declaring constants. You can declare your interface parts like attributes and methods that should not be changed: 66 | 67 | 68 | ```python 69 | from typing_extensions import Final, final 70 | 71 | class BaseAPIDeclaration(object): 72 | namespace: Final = 'api' 73 | 74 | @final 75 | def resolve(self) -> dict: 76 | return {'namespace': self.namespace, 'base': True} 77 | ``` 78 | 79 | Now all subclasses of this imaginary class won't be able to redefine both `namespace` and `resolve()`. But, let's try to hack them to see what happens: 80 | 81 | ```python 82 | class ConcreteAPI(BaseAPIDeclaration): 83 | namespace = 'custom-api' 84 | 85 | def resolve(self) -> dict: 86 | return {'hacking': True} 87 | ``` 88 | 89 | `mypy` will back us up. Here's what the output will look like: 90 | 91 | ``` 92 | » mypy --python-version=3.6 --strict api.py 93 | api.py:12: error: Cannot assign to final name "namespace" 94 | api.py:14: error: Cannot override final attribute "resolve" (previously declared in base class "BaseAPIDeclaration") 95 | ``` 96 | 97 | ## Classes 98 | 99 | And even classes can be `final`. This way we can explicitly forbid to subclass classes not designed to be subclassed: 100 | 101 | ```python 102 | from typing_extensions import final 103 | 104 | @final 105 | class HRBusinessUnit(AbstractBusinessUnit): 106 | def grant_permissions(self) -> None: 107 | self.api.do_some_hr_stuff() 108 | ``` 109 | 110 | What does `@final` decorator bring you? Confidence that nothing will break this contract: 111 | 112 | ```python 113 | class SubHRBusinessUnit(HRBusinessUnit): 114 | def grant_permissions(self) -> None: 115 | self.api.do_some_it_stuff() 116 | ``` 117 | 118 | This code will make `mypy` quite unhappy (please, do not abuse robots!): 119 | 120 | ``` 121 | » mypy --python-version=3.6 --strict units.py 122 | units.py:9: error: Cannot inherit from final class "HRBusinessUnit" 123 | ``` 124 | 125 | Now we can reason about why you should use it in your project. 126 | 127 | ## Conclusion 128 | 129 | Creating new restrictions is good for you: it makes your code cleaner, more readable, and increases its quality. 130 | 131 | Strong points: 132 | 133 | 0. it is clear from the definition what is a constant or a concrete realization and what is not 134 | 1. our users will have strict API boundaries that can not be violated 135 | 2. we can build closed systems that are not tolerant of breaking the rules 136 | 3. it is easier to understand what happens inside your application 137 | 4. it enforces [composition over inheritance](https://en.wikipedia.org/wiki/Composition_over_inheritance), which is a well-known best practice 138 | 139 | Weak points: none! Write a comment if you can find any disadvantages. 140 | 141 | Use types, create nice APIs, keep hacking! 142 | -------------------------------------------------------------------------------- /_posts/2018-12-13-blameless-environment.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Building blameless working environment 4 | description: Blaming people does not work. Let's stop doing that. And find alternative ways to work. 5 | date: 2018-12-13 6 | tags: rsdp management 7 | translated: 8 | - name: Russian 9 | link: https://techrocks.ru/2018/12/15/working-environment-without-blaming-anyone/ 10 | language: ru 11 | republished: 12 | - link: https://dev.to/sobolevn/building-blameless-working-environment--17hl 13 | language: us 14 | --- 15 | 16 | ![Blame](https://thepracticaldev.s3.amazonaws.com/i/6fy52tyxgnx0r1nk6h6i.png) 17 | 18 | Once I was working in the software development company. It was a regular company: nothing really bad, but nothing fancy. It had the same problems as other similar companies: 19 | 20 | - Low-quality code 21 | - Unmet deadlines 22 | - Budget overflows 23 | 24 | And disappointed clients as a final result. 25 | 26 | Of course, everyone in management was eager to know, what (or "who") was the reason. During our workday, it was quite normal to hear something like: "the project is failed because you wrote low-quality code, now the client is moving away from us". 27 | 28 | Did it help in any way? No. Was it pleasant to work there? No. So, once I woke up and decided that I do not want to work in this company anymore. 29 | 30 | The **ugliest** part is that I was the founder of this company. 31 | 32 | 33 | ## Can we please all agree that blaming does not work? 34 | 35 | Before going any further and describing how we did manage to change our environment and how it did affect our results, I would like to point it straight: blaming someone for anything does not work. Why? 36 | 37 | 1. It indicates broken processes exist inside the company's workflow, but instead of fixing them people just point fingers and completely hide the problem. So, it will never be fixed 38 | 2. It demotivates people. You can lose the best engineers because of that. There are companies where people behave differently. And we, engineers, can choose 39 | 40 | What can we do instead? We can build [our processes another way](https://wemake-services.github.io/meta/)! 41 | 42 | 43 | ## Improve your project instead of blaming anyone 44 | 45 | Can you imagine a situation when something goes wrong? When your production stopped working because there was a dependency version mismatch. Or because you made a silly typo in your `string` variable. 46 | 47 | That happens quite often, that's not a surprise. 48 | 49 | ![Bugs everywhere](https://thepracticaldev.s3.amazonaws.com/i/6pquqfacysta1rvvafzs.jpg) 50 | 51 | So, we must be ready to handle that. We must create a working set of rules and practices to dodge possible errors and [handle the ones](https://sobolevn.me/2019/01/how-to-fix-a-bug) that we failed to detect early. 52 | 53 | But how? 54 | 55 | 56 | ## Small tasks and clear scopes 57 | 58 | It usually hard to define big tasks that take days and weeks. This kind of tasks does not have a clear scope and "done" criteria. 59 | 60 | So, that's where things can go wrong. Two people will understand these task differently because of this wider scope. They will prioritize different of the task parts differently. And in the end, it will be easy to say: "You did a bad job, you did not get the task". 61 | 62 | Moreover, it will be hard to review and control the quality of the code. We all know how hard it is to review 1000+ lines of code. 63 | 64 | ![1000 lines to review](https://thepracticaldev.s3.amazonaws.com/i/cck8gyl3ccz8p5qojdh9.png) 65 | 66 | I insist that task should be small (not bigger than 4 hours) and should have a clear scope. It is better to have five small and clearly defined tasks than one big task. 67 | 68 | ![100 lines to review](https://thepracticaldev.s3.amazonaws.com/i/q1m302fa1liwuewvy4tt.png) 69 | 70 | This decreases the possibility of misinterpretation. And allows both sides to track progress easily, control deadlines, and feel the pulse of the project. 71 | 72 | 73 | ## Make your CI as strict as possible 74 | 75 | The sooner you find errors in your code the better it is for the project. This graph illustrates how expensive (in terms of time and money) it is to allow the bug to flow into the production. 76 | 77 | ![Error cost](https://thepracticaldev.s3.amazonaws.com/i/pe75vd3e5ny0nbe74oqg.jpg) 78 | 79 | Bad code should not reach `master` branch and even a human reviewer. And we can automate that! There's static analysis, linting, different kind of tests and other metrics that should pass before. If something is broken - your code just won't make its way to the `master` branch and reviewer won't review it until all checks pass. 80 | 81 | This way creates clear and transparent quality rules that should be met. That's become a law. 82 | 83 | 84 | ## Welcome all bugs 85 | 86 | But errors will still happen! That's a sad reality. But it is your choice how to treat them. You can get angry and depressed because of them, or you can learn from them. 87 | 88 | I prefer to learn from my and others mistakes. How is that possible? 89 | 90 | Whenever something bad happens in production you need to track how your quality gates allowed this to happen. What did we miss? 91 | 92 | After your team finds the answer - you should automate this check to be sure the next time it won't happen again. Create a linting rule or write a [regression test](https://en.wikipedia.org/wiki/Regression_testing). 93 | 94 | Here are some **real world** examples of how we do that. 95 | 96 | Whenever something bad happens for our `python`/`django` project we add a new rule to [the CI process](https://github.com/wemake-services/wemake-django-template/blob/master/%7B%7Bcookiecutter.project_name%7D%7D/docker/ci.sh) or write a new linting rule for [our corporate linter](https://github.com/wemake-services/wemake-python-styleguide). This way we guaranty, that this won't happen again. 97 | 98 | And the same works for [our frontend](https://github.com/wemake-services/wemake-vue-template/blob/master/template/package.json#L28) `javascript`/`vue` projects. 99 | 100 | 101 | ## Make your client a part of the process 102 | 103 | There are cases when you can lose your client not because your code is bad, but because you failed in building a clear communication channel with your client. And then you will be blamed for all of the bad things! 104 | 105 | We had a lot of problems with that in the past. Long iterations and long feedback cycles were killing us. We failed to address bugs and new requirements fast enough. 106 | 107 | The answer to this problem is simple: 108 | 109 | 1. Make your client a part of your development process 110 | 2. Make your iterations as small as possible 111 | 3. Demonstrate your progress as frequently as possible 112 | 113 | We achieve all these goals by several useful practices: 114 | 115 | 1. We invite our clients to the repository from the first day 116 | 2. Microtasking allows us to make several iterations in a single working day 117 | 3. [Gitlab and K8S](https://docs.gitlab.com/ee/topics/autodevops/#auto-review-apps) allow us to make the demo of each individual feature when needed 118 | 119 | We collect feedback from the client as soon as we can. The client always understands what is going on in the project: what is done and what is work-in-progress. 120 | 121 | This way it is hard and pointless to blame anyone, collaborate and build awesome products together. In the end, that is the goal we pursue, isn't it? 122 | 123 | 124 | ## Conclusion 125 | 126 | "No blaming, but fixing" method drastically changed how we work in a good way. 127 | 128 | Since we practice these things we have not failed a single project. And we have not lost a single engineer because of the conflict. 129 | 130 | Of course, this article does not cover all features and tricks that we use in our daily life, but you can read more about how we work at the [`RSDP` (Repeatable Software Development Process) home page](https://wemake-services.github.io/meta/). And you can [follow me on github](https://github.com/sobolevn) to be informed about what tools we build. 131 | -------------------------------------------------------------------------------- /_posts/2019-01-10-announcing-dotenv-linter.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Announcing dotenv-linter" 4 | description: Simple linter for `.env` files. While `.env` files are pretty straight-forward it is required to keep them consistent. This tool offers a wide range of consistency rules and best practices. And it integrates perfectly into any existing workflow. 5 | date: 2019-01-10 6 | tags: python 7 | republished: 8 | - link: https://dev.to/wemake-services/announcing-dotenv-linter-a-tool-to-lint-your-env-files-3m1g 9 | language: us 10 | --- 11 | 12 | I happy to announce a simple yet very useful tool to lint your `.env` files. 13 | 14 | ![dotenv-linter logo](https://thepracticaldev.s3.amazonaws.com/i/untiw080ajgmz84m9857.png) 15 | 16 | 17 | ## Background 18 | 19 | As a part of our ["not blaming but fixing" corporate culture](https://sobolevn.me/2018/12/blameless-environment) we build a lot of tools that prevent us from making the same mistakes over and over again. 20 | 21 | [`dotenv-linter`](https://github.com/wemake-services/dotenv-linter) is one of these tools. 22 | 23 | Some time ago we had several problems with `.env` files: 24 | 25 | - Some developers used `CONSTANT_CASE` for variable names and some developers used `snake_case` for that. While this is not a technical issue, but it is not very practical to mix these two cases and then think: what case I have used for this particular variable? Consistency is important! 26 | - We also had a problem with quotes and extra spaces. Some developers used `KEY=VALUE` and some used `KEY = "VALUE"` while in fact, these two examples will resolve in exactly the same thing - we prefer to have one-- and preferably only one --obvious way to do it. So, we now stick to `KEY=VALUE` notation 27 | - We also once had a duplicate key that ruined my day. [I have spent several hours](https://sobolevn.me/2018/03/mediocre-developer) debugging my app because of this simple issue. That was a turning point for me and I have decided: let's automate it! 28 | 29 | 30 | ## Installation 31 | 32 | You can install it via `pip` (or any other similar tool): 33 | 34 | ```terminal 35 | $ pip install dotenv-linter 36 | ``` 37 | 38 | Why `pip`? Because `python` is present almost on all Linux setups. And we try to make this tool as portable as possible. 39 | 40 | 41 | ## Usage 42 | 43 | Usage is really simple: 44 | 45 | ```terminal 46 | $ dotenv-linter path/to/your/.env even/multiple/files/are/fine/.env 47 | ``` 48 | 49 | 50 | ## Real-life examples 51 | 52 | If you are interested in how we use it real life applications you can have a look at (and even try!) our [`django` template](https://github.com/wemake-services/wemake-django-template). Here's the [line](https://github.com/wemake-services/wemake-django-template/blob/master/%7B%7Bcookiecutter.project_name%7D%7D/docker/ci.sh#L55) that invokes it. 53 | 54 | We also have a full list of [linting rules in our docs](https://dotenv-linter.readthedocs.io/en/latest/pages/violations/index.html), check it out. 55 | 56 | 57 | ## Conclusion 58 | 59 | I hope this simple tool will save you some time, make your project more consistent, and your life slightly better. [Add me on github](https://github.com/sobolevn) to stay informed about the tools I am building! 60 | -------------------------------------------------------------------------------- /_posts/2019-03-03-announcing-docker-image-size-limit.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Announcing docker-image-size-limit" 4 | description: "Keep an eye on your docker image size, limit, and prevent it from growing too big." 5 | date: 2019-03-03 6 | tags: docker python 7 | writing_time: 8 | writing: "0:30" 9 | proofreading: "0:10" 10 | decorating: "0:00" 11 | republished: 12 | - link: https://dev.to/wemake-services/announcing-docker-image-size-limit-keep-an-eye-on-your-docker-image-size-4pm8 13 | language: us 14 | --- 15 | 16 | ![Logo](https://thepracticaldev.s3.amazonaws.com/i/tbud96yztrdmtpbc8u2f.jpeg) 17 | 18 | Repo link: [https://github.com/wemake-services/docker-image-size-limit](https://github.com/wemake-services/docker-image-size-limit) 19 | 20 | ## My story 21 | 22 | It was an early morning. I was drinking my first cup of tea and reviewing a pull request from another developer. 23 | 24 | It looked pretty well. Just some new `python` dependencies to generate beautiful reports. And a bunch of new `alpine` libraries to make sure everything will work. 25 | 26 | So, I have merged it without any hesitations. 27 | 28 | Several hours later I have found out that our image size increased from `~200 MiB` to `~1.5 GiB` in size. This is unacceptable! 29 | 30 | So, I have written this script to restrict the maximum image size in the future: 31 | 32 | {% raw %} 33 | ```bash 34 | LIMIT=1024 35 | IMAGE='your-image-name:latest' 36 | 37 | SIZE="$(docker image inspect "$IMAGE" --format='{{.Size}}')" 38 | test "$SIZE" -gt "$LIMIT" && echo 'Limit exceeded'; false 39 | ``` 40 | {% endraw %} 41 | 42 | It is just like `js` [`size-limit`](https://github.com/ai/size-limit) library, but for `docker`. 43 | 44 | And it worked pretty great. Now, our CI would fail on images that are bigger than our `$LIMIT`. But, do you know that we are using ["Blameless environment"](https://sobolevn.me/2018/12/blameless-environment) method? If something fails, fix it once and for all, including other projects as well. And now I have to distribute this code to all our projects by copy-pasting these four lines. And, of course, it is not how I like my code distribution. 45 | 46 | ## New project 47 | 48 | As a result, I open-sourced this script as a standalone `python` CLI that can be distributed, installed, and used easily: 49 | 50 | ``` 51 | $ pip install docker-image-size-limit 52 | $ disl your-image-name:label 300MiB 53 | your-image-name:label exceeds 300MiB limit by 1200 MiB 54 | ``` 55 | 56 | And that's it. 57 | 58 | Now, you can be sure that your image size will always be in control. Checkout out [the docs](https://github.com/wemake-services/docker-image-size-limit) for all possible options. And integrate it to your CI not to make the same mistakes I have already fixed. 59 | 60 | -------------------------------------------------------------------------------- /_posts/2019-07-19-6-best-mac-apps.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 6 mac apps that fit everyone 4 | description: "You often can see lists of awesome mac apps that you are not going to use or ones that just do not fit you. This one is different. This list contains simple, yet very powerful productivity boosters that totally fits everyone." 5 | date: 2019-07-19 6 | tags: mac 7 | writing_time: 8 | writing: "1:30" 9 | proofreading: "0:10" 10 | decorating: "0:30" 11 | republished: 12 | - link: https://dev.to/sobolevn/6-mac-apps-that-fit-everyone-4b7o 13 | language: us 14 | --- 15 | 16 | You often can see lists of awesome apps that you are not going to use or ones that just do not fit you. 17 | 18 | This one is different. This list contains simple, yet very powerful productivity boosters that totally fits everyone. 19 | 20 | 21 | **TLDR**: Here's the list of all mac apps I am using, they can be found in my [dotfiles](https://github.com/sobolevn/dotfiles/blob/master/Brewfile#L126-L164) with the rest of my setup. 22 | 23 | Previous on-topic articles: 24 | 1. [Command line productivity](https://sobolevn.me/2017/08/instant-command-line-productivity) 25 | 2. [Using better CLIs](https://sobolevn.me/2017/10/using-better-clis) 26 | 27 | 28 | ## Dato 29 | 30 | That's a new project from [@sindresorhus](https://github.com/sindresorhus). It is simple, yet amazing. 31 | For some reason, traditional mac clocks do not show dates and calendar. 32 | So, when someone asks you what date is it today you have to open a calendar app. Seriously? 33 | 34 | This app solves it. It is just an improved clock with calendar and timezones. 35 | 36 | This is a new app, it was just announced: 37 | 38 |

Announcing my latest Mac app:
Dato—A better menu bar clock with calendar and time zones.https://t.co/Div5ozziJb pic.twitter.com/xRPutTfDkt

— Sindre Sorhus (@sindresorhus) 14 июля 2019 г.
39 | 40 | 41 | 42 | You might also want to hide the original clock from your status bar. That's how it can be [done](https://appsliced.co/ask/how-do-i-hide-the-clock-from-my-menu-bar-in-os-x). 43 | 44 | ## HazeOver 45 | 46 | The next app allows highlighting the currently focused window. It sounds not like much, but the feel from this app is amazing. It works great for both light and dark background. Watch this: 47 | 48 | 51 | 52 | Looks awesome, doesn't it? 53 | 54 | ## OpenInTerminal-Lite 55 | 56 | When traveling around your files in Finder you might need to open the current folder inside your Terminal. You can drag-and-drop it to the terminal (which many users do not like) or you can use [OpenInTerminal-Lite](https://github.com/Ji4n1ng/OpenInTerminal). 57 | 58 | It adds new icons to your Finder to open anything inside your Terminal or Editor with a single click: 59 | 60 | ![OpenInTerminal-Lite](https://github.com/Ji4n1ng/OpenInTerminal/raw/659f8447c6a4cd49d9633d1f024abc3288e78bd6/screenshots/run.gif) 61 | 62 | ## Spectacle 63 | 64 | [Spectacle](https://www.spectacleapp.com/) allows using keyboard shortcuts to reorganize windows on your desktop. 65 | 66 | ![Spectacle](https://thepracticaldev.s3.amazonaws.com/i/9hcfzz4gmrzntdlxljyf.gif) 67 | 68 | It is a life-saver for people who have a lot of opened windows and complex workflows. Do you want to have an IDE opened next to your browser? Two key presses. Do you want your terminals sorted? Two key presses. 69 | 70 | As simple as that. 71 | 72 | ## Previews 73 | 74 | That's a whole set of different Quick Preview plugins in [this wonderful repo](https://github.com/sindresorhus/quick-look-plugins). 75 | 76 | They do amazing things to your Quick Preview, like: 77 | 78 | - Highlights your code 79 | ![code highlight](https://github.com/sindresorhus/quick-look-plugins/raw/master/screenshots/QLColorCode.png) 80 | - Allows to view image sizes 81 | ![Image size preview](https://github.com/sindresorhus/quick-look-plugins/raw/master/screenshots/qlImageSize.png) 82 | - Highlights, pretty-prints, and transforms `json` 83 | ![json highlight](https://github.com/sindresorhus/quick-look-plugins/raw/master/screenshots/QuickLookJSON.png) 84 | 85 | ## Flux 86 | 87 | Should I really mention Flux? Because everyone should already have one installed. 88 | 89 | Flux turns your monitor colors to be warm at night. This allows your eyes and brain to rest. [Use it!](https://justgetflux.com/) 90 | 91 | And you can try the demo [look-and-feel online](https://fluxometer.com/rainbow). 92 | 93 | What apps do you use? What problems do they solve? 94 | Provide like to your favorite apps and your dotfiles in the comments! 95 | -------------------------------------------------------------------------------- /_posts/2019-11-21-testing-django-migrations.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Testing Django Migrations 4 | description: "When migrating schema and data in Django multiple things can go wrong. It is better to test what you are doing in advance." 5 | date: 2019-10-13 6 | tags: python django 7 | writing_time: 8 | writing: "0:40" 9 | proofreading: "0:15" 10 | decorating: "0:00" 11 | republished: 12 | - link: https://dev.to/wemake-services/testing-django-migrations-3288 13 | language: us 14 | --- 15 | 16 | Dear internet, 17 | 18 | Today we have screwed up by applying a broken migration to the running production service and causing a massive outage for several hours... Because the rollback function was terribly broken as well. 19 | 20 | As a result, we had to restore a backup that was made several hours ago, losing some new data. 21 | 22 | 23 | ## Why did it happen? 24 | 25 | The easiest answer is just to say: "Because it is X's fault! He is the author of this migration, he should learn how databases work". But, it is counterproductive. 26 | 27 | Instead, as a part of our ["Blameless environment"](https://sobolevn.me/2018/12/blameless-environment) culture, we tend to put all the guilt on the CI. It was the CI who put the broken code into the `master` branch. So, we need to improve it! 28 | 29 | We always [write post-mortems](https://sobolevn.me/2019/01/how-to-fix-a-bug) for all massive incidents that we experience. And we write regression tests for all bugs, so they won't happen again. But, this situation was different, since it was a broken migration that worked during the CI process, and it was hard or impossible to test with the current set of instruments. 30 | 31 | So, let me explain the steps we took to solve this riddle. 32 | 33 | 34 | ## Existing setup 35 | 36 | We use a very strict [`django` project setup](https://github.com/wemake-services/wemake-django-template) with several quality checks for our migrations: 37 | 38 | 1. We write all data migration as typed functions in our main source code. Then we check everything with `mypy` and test as regular functions 39 | 2. We lint migration files with [`wemake-python-styleguide`](https://github.com/wemake-services/wemake-python-styleguide), it drastically reduces the possibility of bad code inside the migration files 40 | 3. We use tests that automatically set up the database by applying all migrations before each session 41 | 4. We use [`django-migration-linter`](https://github.com/3YOURMIND/django-migration-linter) to find migrations that are not suited for zero-time deployments 42 | 5. And then we review the code by two senior people 43 | 6. Then we test everything manually with the help of the review apps 44 | 45 | And somehow it is still not enough: our server was dead. 46 | 47 | When writing the post-mortem for this bug, I spotted that data in our staging and production services were different. And that's why our data migration crushed and left one of the core tables in the broken state. 48 | 49 | So, how can we test migrations on some existing data? 50 | 51 | 52 | ## django-test-migrations 53 | 54 | That's where [`django-test-migrations`](https://github.com/wemake-services/django-test-migrations) comes in handy. 55 | 56 | The idea of this project is simple: 57 | 58 | 1. Set some migration as a starting point 59 | 2. Create some model's data that you want to test 60 | 3. Run the new migration that you are testing 61 | 4. Assert the results! 62 | 63 | Let's illustrate it with some code samples. 64 | Full source code is [available here](https://github.com/wemake-services/django-test-migrations/tree/master/django_test_app). 65 | 66 | Here's the latest version of our model: 67 | 68 | ```python 69 | class SomeItem(models.Model): 70 | """We use this model for testing migrations.""" 71 | 72 | string_field = models.CharField(max_length=50) 73 | is_clean = models.BooleanField() 74 | ``` 75 | 76 | This is a pretty simple model that serves only one purpose: to illustrate the problem. `is_clean` field is related to the contents of `string_field` in some manner. 77 | While the `string_field` itself contains only regular text data. 78 | 79 | Imagine that you have a data migration that looks like so: 80 | 81 | ```python 82 | def _is_clean_item(instance: 'SomeItem') -> bool: 83 | """ 84 | Pure function to the actual migration. 85 | 86 | Ideally, it should be moved to ``main_app/logic/migrations``. 87 | But, as an example it is easier to read them together. 88 | """ 89 | return ' ' not in instance.string_field 90 | 91 | def _set_clean_flag(apps, schema_editor): 92 | """ 93 | Performs the data-migration. 94 | 95 | We can't import the ``SomeItem`` model directly as it may be a newer 96 | version than this migration expects. 97 | 98 | We are using ``.all()`` because 99 | we don't have a lot of ``SomeItem`` instances. 100 | In real-life you should not do that. 101 | """ 102 | SomeItem = apps.get_model('main_app', 'SomeItem') 103 | for instance in SomeItem.objects.all(): 104 | instance.is_clean = _is_clean_item(instance) 105 | instance.save(update_fields=['is_clean']) 106 | 107 | def _remove_clean_flags(apps, schema_editor): 108 | """ 109 | This is just a noop example of a rollback function. 110 | 111 | It is not used in our simple case, 112 | but it should be implemented for more complex scenarios. 113 | """ 114 | 115 | class Migration(migrations.Migration): 116 | dependencies = [ 117 | ('main_app', '0002_someitem_is_clean'), 118 | ] 119 | 120 | operations = [ 121 | migrations.RunPython(_set_clean_flag, _remove_clean_flags), 122 | ] 123 | ``` 124 | 125 | And here's how we are going to test this migration. At first, we will have to set some migration as a starting point: 126 | 127 | ```python 128 | old_state = migrator.before(('main_app', '0002_someitem_is_clean')) 129 | ``` 130 | 131 | Then we have to get the model class. We cannot use direct `import` from `models` because the model might be different, since migrations change them from our stored definition: 132 | 133 | ```python 134 | SomeItem = old_state.apps.get_model('main_app', 'SomeItem') 135 | ``` 136 | 137 | Then we need to create some data that we want to test: 138 | 139 | ```python 140 | # One instance will be `clean`, the other won't be: 141 | SomeItem.objects.create(string_field='a') # clean 142 | SomeItem.objects.create(string_field='a b') # contains whitespace, is not clean 143 | ``` 144 | 145 | Then we will run the migration that we are testing and get the new project state: 146 | 147 | ```python 148 | new_state = migrator.after(('main_app', '0003_auto_20191119_2125')) 149 | SomeItem = new_state.apps.get_model('main_app', 'SomeItem') 150 | ``` 151 | 152 | And the last step: we need to make some assertions on the resulting data. 153 | We have created two model instances before: one clean and one with the whitespace. So, let's check that: 154 | 155 | ```python 156 | assert SomeItem.objects.count() == 2 157 | # One instance is clean, the other is not: 158 | assert SomeItem.objects.filter(is_clean=True).count() == 1 159 | assert SomeItem.objects.filter(is_clean=False).count() == 1 160 | ``` 161 | 162 | And that's how it works! Now we have an ability to test our schema and data transformations with ease. Complete test example: 163 | 164 | ```python 165 | @pytest.mark.django_db 166 | def test_main_migration0002(migrator): 167 | """Ensures that the second migration works.""" 168 | old_state = migrator.before(('main_app', '0002_someitem_is_clean')) 169 | SomeItem = old_state.apps.get_model('main_app', 'SomeItem') 170 | # One instance will be `clean`, the other won't be: 171 | SomeItem.objects.create(string_field='a') 172 | SomeItem.objects.create(string_field='a b') 173 | 174 | assert SomeItem.objects.count() == 2 175 | assert SomeItem.objects.filter(is_clean=True).count() == 2 176 | 177 | new_state = migrator.after(('main_app', '0003_auto_20191119_2125')) 178 | SomeItem = new_state.apps.get_model('main_app', 'SomeItem') 179 | 180 | assert SomeItem.objects.count() == 2 181 | # One instance is clean, the other is not: 182 | assert SomeItem.objects.filter(is_clean=True).count() == 1 183 | ``` 184 | 185 | By the way, we also support raw [`unittest` cases](https://github.com/wemake-services/django-test-migrations#unittest). 186 | 187 | 188 | ## Conclusion 189 | 190 | Don't be sure about your migrations. Test them! 191 | 192 | You can test forward and rollback migrations and their [ordering](https://github.com/wemake-services/django-test-migrations#testing-migrations-ordering) with the help of `django-test-migrations`. It is simple, friendly, and already works with the test framework of your choice. 193 | 194 | I also want to say "thank you" to [these awesome people](https://github.com/wemake-services/django-test-migrations#credits). Without their work it would take me much longer to come up with the working solution. 195 | -------------------------------------------------------------------------------- /_posts/2020-02-25-conditional-coverage.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Conditional coverage" 4 | description: "Sometimes your code has to take different paths based on the external environment. Make sure that your coverage follows it smoothly." 5 | date: 2020-02-25 6 | tags: python 7 | writing_time: 8 | writing: "1:00" 9 | proofreading: "0:20" 10 | decorating: "0:10" 11 | republished: 12 | - link: https://dev.to/wemake-services/cover-the-tricky-parts-of-your-code-24m6 13 | language: us 14 | --- 15 | 16 | ![Cover image](https://dev-to-uploads.s3.amazonaws.com/i/c8c6rwydi6jnjs54vixi.png) 17 | 18 | Recently I had to add `python3.8` for our Python linter (the strictest one in existence): [`wemake-python-styleguide`](https://github.com/wemake-services/wemake-python-styleguide). And during this straight-forward (at first look) task, I have found several problems with test coverage that were not solved in Python community at all. 19 | 20 | Let's dig into it. 21 | 22 | 23 | ## Environment-based logic 24 | 25 | The thing about this update was that `python3.8` introduced a new API. 26 | Previously we had `visit_Num`, `visit_Str`, `visit_NameConstant` methods (we don't really care about what they do, only names are important), but now we have to use `visit_Constant` method instead. 27 | 28 | Well, we will have to write some compatibility layer in our app to support both new `python3.8`, previous `python3.6`, and `python3.7` releases. 29 | 30 | My first attempt was to create a `route_visit` function to do the correct thing for all releases. To make it work I also had to define `PY38` constant like so: 31 | 32 | ```python 33 | import sys 34 | 35 | PY38 = sys.version_info >= (3, 8) 36 | 37 | if PY38: 38 | route_visit = ... # new logic: 3.8+ 39 | else: 40 | route_visit = ... # old logic: 3.6, 3.7 41 | ``` 42 | 43 | And it worked pretty well. I was able to run my tests successfully. The only thing broken was coverage. We use `pytest` and `pytest-cov` to measure coverage in our apps, we also enforce `--cov-branch` and `--cov-fail-under=100` policies. Which enforce us to cover all our code and all branches inside it. 44 | 45 | Here's the problem with my solution: I was either covering `if PY38:` branch on `python3.8` build or `else:` branch on other releases. I was never covering 100% of my program. Because it is literally impossible. 46 | 47 | 48 | ## Common pitfalls 49 | 50 | Open-source libraries usually face this problem. They are required to work with different python versions, 3rd party API changes, backward compatibility, etc. Here are some examples that you probably have already seen somewhere: 51 | 52 | ```python 53 | try: 54 | import django 55 | HAS_DJANGO = True 56 | except ImportError: 57 | HAS_DJANGO = False 58 | ``` 59 | 60 | Or this was a popular hack during `2` / `3` days: 61 | 62 | ```python 63 | try: 64 | range_ = xrange # python2 65 | except NameError: 66 | range_ = range # python3 67 | ``` 68 | 69 | With all these examples in mind, one can be sure that 100% of coverage is not possible. 70 | The common scenario to still achieve a feeling of 100% coverage for these cases was: 71 | 72 | 1. Using [`# pragma: no cover`](https://coverage.readthedocs.io/en/stable/excluding.html) magic comment to exclude a single line or a whole block from coverage 73 | 2. Or writing every compatibility related check in a special `compat.py` that were later [omitted](https://coverage.readthedocs.io/en/stable/source.html#execution) from coverage 74 | 75 | Here's how the first way looks like: 76 | 77 | ```python 78 | try: # pragma: no cover 79 | import django 80 | HAS_DJANGO = True 81 | except ImportError: # pragma: no cover 82 | HAS_DJANGO = False 83 | ``` 84 | 85 | Let's be honest: these solutions are dirty hacks. But, they do work. And I personally used both of them countless times in my life. 86 | 87 | Here's the interesting thing: aren't we supposed to test these complex integrational parts with the most precision and observability? Because that's where our application breaks the most: integration parts. And currently, we are just ignoring them from coverage and pretending that this problem does not exist. 88 | 89 | And for this reason, this time I felt like I am not going to simply exclude my compatibility logic. I got an idea for a new project. 90 | 91 | 92 | ## Conditional coverage 93 | 94 | My idea was that `# pragma` comments can have more information inside them. Not just `no cover`, but `no cover when?`. That's how [`coverage-conditional-plugin`](https://github.com/wemake-services/coverage-conditional-plugin) was born. Let's use it and see how it works! 95 | 96 | First, we would need to install it: 97 | 98 | ```bash 99 | pip install coverage-conditional-plugin # pip works, but I prefer poetry 100 | ``` 101 | 102 | And then we would have to [configure](https://coverage.readthedocs.io/en/coverage-5.0.3/config.html) `coverage` and the plugin itself: 103 | 104 | ```yaml 105 | [coverage:run] 106 | # Here we specify plugins for coverage to be used: 107 | plugins = 108 | coverage_conditional_plugin 109 | 110 | [coverage:coverage_conditional_plugin] 111 | # Here we specify our pragma rules: 112 | rules = # we are going to define them later. 113 | ``` 114 | 115 | Notice this `rules` key. It is the most important thing here. The rule (in this context) is some predicate that tells: should we include lines behind this specific `pragma` in our coverage or not. Here are some examples: 116 | 117 | ```yaml 118 | [coverage:coverage_conditional_plugin] 119 | # Here we specify our pragma rules: 120 | rules = 121 | "sys_version_info >= (3, 8)": py-gte-38 122 | "sys_version_info < (3, 8)": py-lt-38 123 | "is_installed('django')": has-django 124 | "not is_installed('django')": has-no-django 125 | ``` 126 | 127 | It is pretty clear what we are doing here: we are defining pairs of predicates to include this code if some condition is true and another code in the opposite case. 128 | 129 | Here's how our previous examples would look like with these magic comments: 130 | 131 | ```python 132 | import sys 133 | 134 | PY38 = sys.version_info >= (3, 8) 135 | 136 | if PY38: # pragma: py-lt-38 137 | route_visit = ... # new logic: 3.8+ 138 | else: # pragma: py-gte-38 139 | route_visit = ... # old logic: 3.6, 3.7 140 | ``` 141 | 142 | What does it say? If we are running on `py-lt-38` ignore `if PY38:` part. But, cover `else:` case. Because it is going to be executed and we know it. And we need to know how good did we cover it. On the other hand, if we are running on `py-gte-38` then cover `if PY38:` case and leave `else:` alone. 143 | 144 | And we can test that everything works correctly. Let's add some nonsense into our `PY38` branch to see if it is going to be covered by `python3.8` build: 145 | 146 | ![PY38 Covered](https://dev-to-uploads.s3.amazonaws.com/i/ul5t9zxjc5omzvu16qxn.png) 147 | 148 | As we can see: green signs show which lines were fully covered, the yellow line indicates that branch coverage was not full, and the red line indicates that the line was not covered at all. And here's an example of grey or ignored lines under the opposed condition: 149 | 150 | ![py-lt-38 ignored](https://dev-to-uploads.s3.amazonaws.com/i/ebcok1i4wgmxftez0sjy.png) 151 | 152 | [Here](https://github.com/wemake-services/wemake-python-styleguide/blob/master/wemake_python_styleguide/compat/routing.py) you can find the full real-life source code for this sample. 153 | 154 | And here's one more example with `django` to show you how external packages can be handled: 155 | 156 | ```python 157 | try: # pragma: has-no-django 158 | import django 159 | HAS_DJANGO = True 160 | except ImportError: # pragma: has-django 161 | HAS_DJANGO = False 162 | ``` 163 | 164 | We use the same logic here. Do we have `django` installed during tests (we have a little helper function `is_installed` to tell us)? If so, cover `try:`. If not, cover `except ImportError:` branch. But always cover *something*. 165 | 166 | 167 | ## Conclusion 168 | 169 | I hope you got the idea. Conditional coverage allows you to add or ignore lines based on predicates and collecting required bits of coverage from every run, not just ignoring complex conditions and keeping our eyes wide shut. Remember, that the code we need to cover the most! 170 | 171 | By the way, we have all kinds of helpers to query your environment: 172 | 173 | - `os_environ` for env variables 174 | - `patform_release` and `platform_version` for OS-based values 175 | - `pkg_version` that returns package version information (as its name says) 176 | - and many others! 177 | 178 | This little plugin is going to be really helpful for library authors that have to deal with compatibility and unfixed environments. And `coverage-conditional-plugin` will surely *cover* their backs! [Give it a star](https://github.com/wemake-services/coverage-conditional-plugin) on Github if you like this idea. And read the project docs to learn more. 179 | -------------------------------------------------------------------------------- /_posts/2021-12-31-paramspec-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Python ParamSpec guide" 4 | description: "Newly released feature in PEP612 allows you do a lot of advanced typing things with functions and their signatures" 5 | date: 2021-12-31 6 | tags: python 7 | writing_time: 8 | writing: "0:30" 9 | proofreading: "0:10" 10 | decorating: "0:05" 11 | republished: [] 12 | translated: [] 13 | --- 14 | 15 | Before `ParamSpec` ([PEP612](https://www.python.org/dev/peps/pep-0612/)) was released in Python3.10 and `typing_extensions`, 16 | there was a big problem in typing decorators that change a function's signature. 17 | 18 | Let's start with a basic example. How one can type a decorator function that does not change anything? 19 | 20 | ```python 21 | from typing import Callable, TypeVar 22 | 23 | C = TypeVar('C', bound=Callable) 24 | 25 | def logger(function: C) -> C: 26 | def decorator(*args, **kwargs): 27 | print('Function called!') 28 | return function(*args, **kwargs) 29 | return decorator 30 | ``` 31 | 32 | Notice the most important part here: `C = TypeVar('C', bound=Callable)` 33 | 34 | What does it mean? It means that we take any callable in and return the exact same callable. 35 | 36 | This allows you to decorate any function and preserve its signature: 37 | 38 | ```python 39 | @logger 40 | def example(arg: int, other: str) -> tuple[int, str]: 41 | return arg, other 42 | 43 | reveal_type(example) # (arg: int, other: str) -> tuple[int, str] 44 | ``` 45 | 46 | But, there's a problem when a function does want to change something. 47 | Imagine, that some decorator might also add `None` as a return value in some cases: 48 | 49 | ```python 50 | def catch_exception(function): 51 | def decorator(*args, **kwargs): 52 | try: 53 | return function(*args, **kwargs) 54 | except Exception: 55 | return None 56 | return decorator 57 | ``` 58 | 59 | This is a perfectly valid Python code. 60 | But how can we type it? Note that we cannot use `TypeVar('C', bound=Callable)` anymore, since we are changing the return type now. 61 | 62 | Initially, I've tried something like: 63 | 64 | ```python 65 | def catch_exception(function: Callable[..., T]) -> Callable[..., Optional[T]]: 66 | ... 67 | ``` 68 | 69 | But, this means a different thing: it turns all function's arguments into `*args: Any, **kwargs: Any`, but, the return type will be correct. Generally, this is not what we need when it comes to type-safety. 70 | 71 | The second way to do that in a type-safe way is adding a custom Mypy plugin. 72 | Here's our example from [`dry-python/returns`](https://github.com/dry-python/returns) to support decorators that [were changing return types](https://github.com/dry-python/returns/blob/0.17.0/returns/contrib/mypy/_features/decorators.py). But, plugins are quite hard to write (you need to learn a bit of Mypy's API), they are not universal (for example, Pyright does not understand Mypy plugins), and they require to be [explicitly installed](https://returns.readthedocs.io/en/latest/pages/contrib/mypy_plugins.html) by the end user. 73 | 74 | That's why `ParamSpec` was added. Here's how it can be used in this case: 75 | 76 | ```python 77 | from typing import Callable, TypeVar, Optional 78 | from typing_extensions import ParamSpec # or `typing` for `python>=3.10` 79 | 80 | T = TypeVar('T') 81 | P = ParamSpec('P') 82 | 83 | def catch_exception(function: Callable[P, T]) -> Callable[P, Optional[T]]: 84 | def decorator(*args: P.args, **kwargs: P.kwargs) -> Optional[T]: 85 | try: 86 | return function(*args, **kwargs) 87 | except Exception: 88 | return None 89 | return decorator 90 | ``` 91 | 92 | Now, all decorated functions will preserve their argument types and change their return type to include `None`: 93 | 94 | ```python 95 | @catch_exception 96 | def div(arg: int) -> float: 97 | return arg / arg 98 | 99 | reveal_type(div) # (arg: int) -> Optional[float] 100 | 101 | @catch_exception 102 | def plus(arg: int, other: int) -> int: 103 | return arg + other 104 | 105 | reveal_type(plus) # (arg: int, other: int) -> Optional[int]: 106 | ``` 107 | 108 | The recent release of Mypy 0.930 with `ParamSpec` support allowed us to remove our custom Mypy plugin and use a well-defined primitive. Here's [a commit to show](https://github.com/dry-python/returns/commit/32aa73f852ef2ffb5ff4664b0d6e0ac2ebd71017) how easy our transition was. It was even released today in [`returns@0.18.0`](https://github.com/dry-python/returns/releases/tag/0.18.0), check it out! 109 | 110 | ## What's next? Concatenate 111 | 112 | But, that's not all! Because some decorators modify argument types, PEP612 also adds the `Concatenate` type that allows prepending, appending, transforming, or removing function arguments. 113 | 114 | Unfortunately, Mypy does not support `Concatenate` just yet, but I can show you some examples from PEP itself. Here's how it is going to work. 115 | 116 | Let's start with some basic definitions: 117 | 118 | ```python 119 | from typing_extensions import ParamSpec, Concatenate # or `typing` for `python>=3.10` 120 | 121 | P = ParamSpec('P') 122 | 123 | def bar(x: int, *args: bool) -> int: ... 124 | ``` 125 | 126 | We are going to change the type of `bar` function with the help of `P` parameter specification. First, let's prepend an `str` argument to this function: 127 | 128 | ```python 129 | def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], int]: ... 130 | 131 | add(bar) # (str, /, x: int, *args: bool) -> int 132 | ``` 133 | 134 | Notice that a positional-only `str` argument is added to the return type of `add(bar)`. 135 | Now, let's try removing an argument: 136 | 137 | ```python 138 | def remove(x: Callable[Concatenate[int, P], int]) -> Callable[P, int]: ... 139 | 140 | remove(bar) # (*args: bool) -> int 141 | ``` 142 | 143 | Because we use `P` and `Concatenate` in the argument type, the return type will not have an `int` argument anymore. 144 | 145 | And finally, let's change an argument type from `int` to `str` and return type from `int` to `bool`: 146 | 147 | ```python 148 | def transform( 149 | x: Callable[Concatenate[int, P], int] 150 | ) -> Callable[Concatenate[str, P], bool]: ... 151 | 152 | transform(bar) # (str, /, *args: bool) -> bool 153 | ``` 154 | 155 | Looking forward to new Mypy release with `Concatenate` support. I totally know some places where it will be useful. 156 | 157 | ## Conclusion 158 | 159 | PEP612 adds two very powerful abstractions that allow us to better type our functions and decorators, which play a very important role in Python's world. 160 | 161 | Complex projects (like [Django](https://github.com/typeddjango/django-stubs)) or simple type-safe scripts can highly benefit from this new typing feature. And I hope you will! 162 | 163 | Happy New Year! 164 | -------------------------------------------------------------------------------- /_sass/base/_reset.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Reset 3 | ========================================================================== */ 4 | 5 | /* Reset Modified from Normalize.css */ 6 | 7 | /* Base Reset */ 8 | 9 | * { 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | *, *:before, *:after { box-sizing: inherit; } 15 | 16 | 17 | html { 18 | min-height: 100%; 19 | box-sizing: border-box; 20 | -webkit-tap-highlight-color: transparent; 21 | font-size: 100%; // Make it easy to calculate rems to px 22 | background: #fff; 23 | } 24 | 25 | body { 26 | @include ff--serif(400); 27 | } 28 | 29 | article, 30 | aside, 31 | figcaption, 32 | figure, 33 | footer, 34 | header, 35 | hgroup, 36 | main, 37 | menu, 38 | nav, 39 | section { 40 | display: block; 41 | } 42 | 43 | header a { 44 | padding: 5px 1px; 45 | } 46 | 47 | h1, 48 | h2, 49 | h3, 50 | h4, 51 | h5, 52 | h6 { 53 | @include ff--sans-serif(800); 54 | color: $c-base__02; 55 | 56 | a { 57 | @include ff--sans-serif(800); 58 | color: $c-base__02; 59 | } 60 | } 61 | 62 | 63 | /* Media */ 64 | audio, canvas, progress, video { 65 | display: inline-block; 66 | vertical-align: baseline; 67 | } 68 | 69 | audio:not([controls]) { 70 | display: none; 71 | height: 0; 72 | } 73 | 74 | /* Images */ 75 | 76 | [hidden], template { display: none; } 77 | 78 | img { 79 | max-width: 100%; 80 | font-style: italic; 81 | vertical-align: middle; 82 | border: 0; 83 | } 84 | 85 | svg:not(:root) { overflow: hidden; } 86 | 87 | svg { 88 | pointer-events: none; 89 | max-height: 100% 90 | } 91 | 92 | .center { 93 | text-align: center; 94 | } 95 | 96 | /* Links */ 97 | 98 | a:not(.anchor) { 99 | color: $c-base__03; 100 | background-color: transparent; 101 | text-decoration: none; 102 | 103 | position: relative; 104 | display: inline-block; 105 | padding: 0px 1px; 106 | transition: color ease 0.3s; 107 | 108 | /* Hover animation effect for all buttons */ 109 | &::after { 110 | content: ''; 111 | position: absolute; 112 | z-index: -1; 113 | height: 0%; 114 | left: -1.5px; 115 | right: -1.5px; 116 | bottom: 0; 117 | background-color: $c-base__03; 118 | transition: all ease 0.3s; 119 | } 120 | 121 | code ~ &::after { 122 | left: 0; 123 | right: 0; 124 | } 125 | 126 | &:hover { 127 | color: white; 128 | &::after { 129 | height: 100%; 130 | } 131 | } 132 | 133 | } 134 | 135 | a.anchor { 136 | text-decoration: none; 137 | } 138 | 139 | /* Override hover animation with no-hov class */ 140 | a.no-hov { 141 | &:after { 142 | content: none; 143 | } 144 | 145 | &:hover { 146 | color: $c-base__03; 147 | } 148 | } 149 | 150 | a.nav { 151 | padding: 10px 35px; 152 | overflow:hidden; 153 | } 154 | a.nav:before { 155 | font-family: FontAwesome; 156 | content:"\f07a"; 157 | position: absolute; 158 | top: 11px; 159 | left: -30px; 160 | transition: all 200ms ease; 161 | } 162 | 163 | abbr[title] { border-bottom: 1px dotted; } 164 | b, strong { font-weight: bold; } 165 | i, em { font-weight: italic; } 166 | 167 | /* Content */ 168 | 169 | figure { 170 | margin: 0; 171 | } 172 | 173 | 174 | hr { 175 | margin-top: 2.5rem; 176 | margin-bottom: 2.5rem; 177 | width: 100%; 178 | height: 1px; 179 | border: 0; 180 | background: #EFF1F3; 181 | } 182 | 183 | /* Code Blocks */ 184 | 185 | pre { 186 | overflow: auto; 187 | padding: 10px; 188 | } 189 | 190 | :not(pre) > code { 191 | background-color: rgba(27,31,35,.05); 192 | border-radius: 3px; 193 | font-size: 85%; 194 | margin: 0 -0.4em; 195 | padding: .2em .4em; 196 | } 197 | 198 | small { 199 | color: gray; 200 | } 201 | 202 | -------------------------------------------------------------------------------- /_sass/components/_archives.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Archives 3 | ========================================================================== */ 4 | 5 | .c-archives { 6 | margin-bottom: 10rem; 7 | } 8 | 9 | .c-summary { 10 | margin-bottom: 5rem; 11 | line-height: 1.7; 12 | } 13 | 14 | .c-archives__year { 15 | margin-bottom: 2.5rem; 16 | @include fs--heading-2; 17 | } 18 | 19 | .c-archives__list { 20 | margin-bottom: 2.5rem; 21 | list-style: none; 22 | } 23 | 24 | .c-archives__item { 25 | padding-top: 2.5rem; 26 | padding-bottom: 2.5rem; 27 | border-top: 1px solid #EFF1F3; 28 | 29 | @media screen and (min-width: $bp__sm) { 30 | display: -webkit-box; 31 | display: -webkit-flex; 32 | display: -ms-flexbox; 33 | display: flex; 34 | 35 | -webkit-flex-direction: column; 36 | -ms-flex-direction: column; 37 | flex-direction: column; 38 | } 39 | 40 | &.c-archives__item--squashed { 41 | padding-top: 1.8rem; 42 | padding-bottom: 1.8rem; 43 | } 44 | 45 | .c-archives__description { 46 | margin-top: 1rem; 47 | } 48 | 49 | h3 { 50 | font-weight: 800; 51 | text-rendering: optimizeLegibility; 52 | font-size: 30px; 53 | line-height: 1.1; 54 | 55 | small { 56 | font-weight: normal; 57 | font-size: 15px; 58 | } 59 | } 60 | 61 | p { 62 | @include fs--body; 63 | color: #515862; 64 | 65 | small { 66 | margin: 0; 67 | text-transform: lowercase; 68 | 69 | + small { 70 | margin-left: 1rem; 71 | } 72 | } 73 | } 74 | 75 | a { 76 | text-transform: capitalize; 77 | } 78 | } 79 | 80 | a[data-disqus-identifier] { 81 | text-transform: lowercase; 82 | } 83 | -------------------------------------------------------------------------------- /_sass/components/_article.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Article 3 | ========================================================================== */ 4 | 5 | .c-article { 6 | margin-bottom: 10rem; // 100px 7 | } 8 | 9 | .c-article__header { 10 | margin-bottom: 2rem; // 50px 11 | @media screen and (min-width: $bp__sm) { 12 | display: -webkit-box; 13 | display: -webkit-flex; 14 | display: -ms-flexbox; 15 | display: flex; 16 | -webkit-flex-wrap: wrap; 17 | -ms-flex-wrap: wrap; 18 | flex-wrap: wrap; 19 | -webkit-box-pack: justify; 20 | -webkit-justify-content: space-between; 21 | -ms-flex-pack: justify; 22 | justify-content: space-between; 23 | -webkit-box-align: baseline; 24 | -webkit-align-items: baseline; 25 | -ms-flex-align: baseline; 26 | align-items: baseline; 27 | } 28 | } 29 | 30 | .c-article__title { 31 | @include fs--heading-1; 32 | color: $c-base__02; 33 | display: block; 34 | width: 100%; 35 | } 36 | 37 | .c-article__time { 38 | @include fs--body; 39 | color: $c-base__01; 40 | 41 | small { 42 | margin: 0; 43 | text-transform: lowercase; 44 | 45 | + small { 46 | margin-left: 1rem; 47 | } 48 | } 49 | } 50 | 51 | .c-article__main { 52 | @include fs--body; 53 | 54 | font-size: 1.1rem; 55 | margin-bottom: 3rem; // 50px 56 | color: $c-base__01; 57 | 58 | h2 { 59 | @include fs--heading-2; 60 | margin: 2em 0 0.5em; 61 | } 62 | 63 | h3 { 64 | @include fs--heading-3; 65 | margin: 1em 0 0.5em; 66 | } 67 | 68 | h4 { 69 | @include fs--heading-4; 70 | } 71 | 72 | h5 { 73 | @include fs--body; 74 | } 75 | 76 | strong { 77 | color: $c-base__02; 78 | font-weight: 700; 79 | } 80 | 81 | blockquote { 82 | margin-left: 0; 83 | margin-right: 0; 84 | padding-left: 1.8rem; // 18px 85 | border-left: 5px solid #ccc; 86 | } 87 | 88 | ul, 89 | ol { 90 | margin-left: 2.1rem; 91 | 92 | img { 93 | display: inline-block; 94 | vertical-align: middle; 95 | } 96 | } 97 | 98 | ul { 99 | list-style: none; 100 | 101 | li { 102 | position: relative; 103 | 104 | &:before { 105 | content: "\25BA"; 106 | left: -20px; 107 | position: absolute; 108 | font-size: 10px; 109 | line-height: 28px; 110 | } 111 | } 112 | } 113 | 114 | li { 115 | p { padding: 5px 0; } 116 | } 117 | 118 | img { 119 | margin: 0 auto; 120 | display: block; 121 | } 122 | 123 | blockquote, 124 | ul, 125 | ol, 126 | p, 127 | li, 128 | table, 129 | code, 130 | div.highlighter-rouge { 131 | margin-bottom: 0.75rem; 132 | } 133 | 134 | :not(pre) > code { 135 | margin: 0; 136 | display: inline; 137 | } 138 | 139 | table { 140 | border-collapse: collapse; 141 | 142 | tr { 143 | border: 1px inset; 144 | } 145 | 146 | th, td { 147 | padding: 10px 5px; 148 | } 149 | } 150 | 151 | .youtube-video, 152 | .twitter-tweet { 153 | margin: 0 auto; 154 | display: block; 155 | margin-bottom: 1rem; 156 | } 157 | 158 | .web-container { 159 | position: relative; 160 | width: 100%; 161 | max-width: 100%; 162 | height: 0; 163 | padding-bottom: 62.5%; 164 | border: 1px solid #cacaca; 165 | overflow: hidden; 166 | } 167 | 168 | .web-container iframe { 169 | position: absolute; 170 | top: 0; 171 | left: 0; 172 | width: 100%; 173 | max-width: 100%; 174 | height: 100%; 175 | } 176 | 177 | .post-subscribe { 178 | text-align: center; 179 | } 180 | 181 | .post-republish { 182 | ul { 183 | li { 184 | margin-bottom: 0; 185 | 186 | &:before { 187 | line-height: 32px; 188 | } 189 | } 190 | } 191 | 192 | a { 193 | font-size: 15px; 194 | } 195 | } 196 | 197 | .post-translate { 198 | ul { 199 | margin-left: 0; 200 | 201 | li { 202 | display: inline-block; 203 | 204 | &:before { 205 | display: none; 206 | visibility: hidden; 207 | } 208 | } 209 | } 210 | } 211 | } 212 | 213 | .c-article__repost { 214 | img { 215 | display: inline-block; 216 | } 217 | } 218 | 219 | .c-article__subscribe { 220 | width: 100%; 221 | height: 660px; 222 | } 223 | 224 | @mixin input() { 225 | height: 34px; 226 | padding: 6px 12px; 227 | font-size: 14px; 228 | line-height: 1.42857143; 229 | color: #555; 230 | background-color: #fff; 231 | background-image: none; 232 | border: 1px solid #ccc; 233 | border-radius: 4px; 234 | } 235 | 236 | .subscribe-form { 237 | form { 238 | border: 1px solid #ccc; 239 | padding: 1em; 240 | text-align: center; 241 | } 242 | 243 | .subscribe-form__input-text { 244 | @include input(); 245 | width: 50%; 246 | min-width: 140px; 247 | } 248 | 249 | .subscribe-form__input-submit { 250 | @include input(); 251 | } 252 | 253 | .subscribe-form__input-submit:hover, 254 | .subscribe-form__input-submit:active { 255 | color: white; 256 | background-color: #d23669; 257 | border-color: #d23669; 258 | cursor: pointer; 259 | } 260 | } 261 | 262 | .pagenav { 263 | width: 100%; 264 | text-align: center; 265 | border: 1px solid LightGrey; 266 | border-left-color: transparent; 267 | border-right-color: transparent; 268 | font-size: 18px; 269 | overflow: hidden; 270 | margin-bottom: 8rem; 271 | } 272 | 273 | .pagenav div { 274 | border-color: transparent; 275 | } 276 | 277 | .wrapper { 278 | padding: 10px; 279 | border: 1px solid LightGrey; 280 | display: inline-block; 281 | margin: 0 auto; 282 | } 283 | 284 | #left { 285 | float: left; 286 | text-align: left; 287 | } 288 | 289 | #right { 290 | float: right; 291 | text-align: right; 292 | } 293 | 294 | -------------------------------------------------------------------------------- /_sass/components/_page.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Page 3 | ========================================================================== */ 4 | 5 | .c-page__subtitle { 6 | @include fs--heading-1; 7 | margin-bottom: 2.5rem; // 25px 8 | max-width: 100%; 9 | word-break: break-word; 10 | } 11 | 12 | .c-page__header { 13 | margin-bottom: 6rem; // 100px 14 | 15 | h1 { 16 | margin-bottom: 2.5rem; // 25px 17 | @include fs--heading-2; 18 | color: $c-base__02; 19 | } 20 | 21 | nav { 22 | @include fs--meta; 23 | } 24 | 25 | a { 26 | line-height: 1.5; 27 | text-transform: lowercase; 28 | } 29 | } 30 | 31 | .c-page__footer { 32 | margin-bottom: 10rem; 33 | display: -webkit-box; 34 | display: -webkit-flex; 35 | display: -ms-flexbox; 36 | display: flex; 37 | -webkit-flex-wrap: wrap; 38 | -ms-flex-wrap: wrap; 39 | flex-wrap: wrap; 40 | -webkit-box-pack: justify; 41 | -webkit-justify-content: space-between; 42 | -ms-flex-pack: justify; 43 | justify-content: space-between; 44 | -webkit-box-align: center; 45 | -webkit-align-items: center; 46 | -ms-flex-align: center; 47 | align-items: center; 48 | p { 49 | @include fs--body; 50 | color: $c-base__01; 51 | } 52 | } 53 | 54 | @media screen and (max-width: 500px) { 55 | .c-page__subtitle { 56 | font-size: 1.8em; 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /_sass/components/_tag.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Tip 3 | ========================================================================== */ 4 | 5 | .c-tags { 6 | list-style: none; 7 | margin: 0; 8 | overflow: hidden; 9 | padding: 0; 10 | } 11 | 12 | .c-tags li { 13 | float: left; 14 | } 15 | 16 | .c-tag { 17 | background: #eee; 18 | border-radius: 3px 0 0 3px; 19 | color: #999; 20 | display: inline-block; 21 | height: 26px; 22 | line-height: 26px; 23 | padding: 0 20px 0 23px; 24 | position: relative; 25 | margin: 0 10px 40px 0; 26 | text-decoration: none; 27 | -webkit-transition: color 0.2s; 28 | } 29 | 30 | .c-tag::before { 31 | background: #fff; 32 | border-radius: 10px; 33 | box-shadow: inset 0 1px rgba(0, 0, 0, 0.25); 34 | content: ''; 35 | height: 6px; 36 | left: 10px; 37 | position: absolute; 38 | width: 6px; 39 | top: 10px; 40 | } 41 | 42 | .c-tag::after { 43 | background: #fff; 44 | border-bottom: 13px solid transparent; 45 | border-left: 10px solid #eee; 46 | border-top: 13px solid transparent; 47 | content: ''; 48 | position: absolute; 49 | right: 0; 50 | top: 0; 51 | } 52 | 53 | .c-tag:hover { 54 | background-color: $c-base__03; 55 | color: white; 56 | } 57 | 58 | .c-tag:hover::after { 59 | border-left-color: $c-base__03; 60 | } 61 | 62 | .c-c-tag { 63 | margin-right: 1rem; 64 | position: relative; 65 | white-space: nowrap; 66 | @include fs--body; 67 | &:before { 68 | color: $c-base__0; 69 | content: '#\2009'; 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /_sass/helpers/_mixins.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Mixins 3 | ========================================================================== */ 4 | 5 | // Clearfix 6 | @mixin clearfix { 7 | &:before, 8 | &:after { 9 | content: " "; 10 | display: table; 11 | } 12 | &:after { 13 | clear: both; 14 | } 15 | } 16 | 17 | // Font families 18 | @mixin ff--sans-serif($font-weight: normal) { 19 | font-family: 'Montserrat', sans-serif;; 20 | font-weight: $font-weight; 21 | } 22 | 23 | @mixin ff--serif($font-weight: normal) { 24 | font-family: 'Merriweather', serif; 25 | font-weight: $font-weight; 26 | } 27 | 28 | @mixin ff--code { 29 | font-family: 'Roboto Mono', monospace; 30 | } 31 | 32 | // Font sizing 33 | @mixin fs--heading-1 { 34 | line-height: 1.5; 35 | font-size: 3rem; 36 | } 37 | 38 | @mixin fs--heading-2 { 39 | line-height: 1.5; 40 | font-size: 1.7rem; 41 | } 42 | 43 | @mixin fs--heading-3 { 44 | line-height: 1.5; 45 | font-size: 1.3rem; 46 | } 47 | 48 | @mixin fs--heading-4 { 49 | line-height: 1.6; 50 | font-size: 1.1rem; 51 | } 52 | 53 | @mixin fs--body { 54 | line-height: 28px; 55 | font-size: 1rem; 56 | } 57 | 58 | @mixin fs--meta { 59 | line-height: 1; 60 | font-size: 1rem; 61 | } 62 | 63 | @mixin fs--caption { 64 | line-height: 1; 65 | font-size: 0.8rem; 66 | } 67 | 68 | @mixin fs--code { 69 | font-size: 1rem; 70 | line-height: 1.5; 71 | } 72 | 73 | // Visually hide content 74 | @mixin visually-hidden { 75 | position: absolute; 76 | margin: -1px; 77 | border: 0; 78 | padding: 0; 79 | width: 1px; 80 | height: 1px; 81 | overflow: hidden; 82 | clip: rect(0 0 0 0); 83 | } 84 | 85 | -------------------------------------------------------------------------------- /_sass/helpers/_variables.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Variabeles 3 | ========================================================================== */ 4 | 5 | $c-base__03: #d23669; 6 | $c-base__02: #163541; 7 | $c-base__01: #404040; 8 | $c-base__0: #869395; 9 | 10 | // Breakpoints 11 | $bp__sm: 45rem; // 450px 12 | -------------------------------------------------------------------------------- /_sass/utilities/_layout.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Layout 3 | ========================================================================== */ 4 | 5 | .u-container { 6 | max-width: 56rem; 7 | margin-right: auto; 8 | margin-left: auto; 9 | padding-top: 6rem; 10 | padding-right: 1rem; 11 | padding-left: 1rem; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /_sass/utilities/_separator.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | Separator 3 | ========================================================================== */ 4 | 5 | .u-separate { 6 | margin-right: .45rem; 7 | margin-left: .25rem; 8 | color: $c-base__01; 9 | &:after { 10 | content: '\00a0/'; 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /_sass/vendor/_highlight.scss: -------------------------------------------------------------------------------- 1 | pre, code { 2 | @include ff--code; 3 | @include fs--code; 4 | } 5 | 6 | .highlight { 7 | border-radius: 4px; 8 | background: #FDFDFD; 9 | border: 1px solid #E8E8EB; 10 | color: #93a1a1; 11 | 12 | .gutter { 13 | padding: 1.2rem; 14 | border-right: 1px solid #E8E8EB; 15 | } 16 | 17 | .code { 18 | padding: 1.2rem; 19 | } 20 | 21 | code { 22 | color: #f8f8f2; 23 | } 24 | } 25 | 26 | span.lineno { 27 | padding: 1rem; 28 | border-right: 1px solid #E8E8EB; 29 | } 30 | 31 | .highlight pre { background-color: #272822; } 32 | .highlight .hll { background-color: #272822; } 33 | .highlight .c { color: #75715e } /* Comment */ 34 | .highlight .err { color: #960050; background-color: #1e0010 } /* Error */ 35 | .highlight .k { color: #66d9ef } /* Keyword */ 36 | .highlight .l { color: #ae81ff } /* Literal */ 37 | .highlight .n { color: #f8f8f2 } /* Name */ 38 | .highlight .o { color: #f92672 } /* Operator */ 39 | .highlight .p { color: #f8f8f2 } /* Punctuation */ 40 | .highlight .cm { color: #75715e } /* Comment.Multiline */ 41 | .highlight .cp { color: #75715e } /* Comment.Preproc */ 42 | .highlight .c1 { color: #75715e } /* Comment.Single */ 43 | .highlight .cs { color: #75715e } /* Comment.Special */ 44 | .highlight .ge { font-style: italic } /* Generic.Emph */ 45 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 46 | .highlight .kc { color: #66d9ef } /* Keyword.Constant */ 47 | .highlight .kd { color: #66d9ef } /* Keyword.Declaration */ 48 | .highlight .kn { color: #f92672 } /* Keyword.Namespace */ 49 | .highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ 50 | .highlight .kr { color: #66d9ef } /* Keyword.Reserved */ 51 | .highlight .kt { color: #66d9ef } /* Keyword.Type */ 52 | .highlight .ld { color: #e6db74 } /* Literal.Date */ 53 | .highlight .m { color: #ae81ff } /* Literal.Number */ 54 | .highlight .s { color: #e6db74 } /* Literal.String */ 55 | .highlight .na { color: #a6e22e } /* Name.Attribute */ 56 | .highlight .nb { color: #f8f8f2 } /* Name.Builtin */ 57 | .highlight .nc { color: #a6e22e } /* Name.Class */ 58 | .highlight .no { color: #66d9ef } /* Name.Constant */ 59 | .highlight .nd { color: #a6e22e } /* Name.Decorator */ 60 | .highlight .ni { color: #f8f8f2 } /* Name.Entity */ 61 | .highlight .ne { color: #a6e22e } /* Name.Exception */ 62 | .highlight .nf { color: #a6e22e } /* Name.Function */ 63 | .highlight .nl { color: #f8f8f2 } /* Name.Label */ 64 | .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ 65 | .highlight .nx { color: #a6e22e } /* Name.Other */ 66 | .highlight .py { color: #f8f8f2 } /* Name.Property */ 67 | .highlight .nt { color: #f92672 } /* Name.Tag */ 68 | .highlight .nv { color: #f8f8f2 } /* Name.Variable */ 69 | .highlight .ow { color: #f92672 } /* Operator.Word */ 70 | .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ 71 | .highlight .mf { color: #ae81ff } /* Literal.Number.Float */ 72 | .highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ 73 | .highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ 74 | .highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ 75 | .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ 76 | .highlight .sc { color: #e6db74 } /* Literal.String.Char */ 77 | .highlight .sd { color: #e6db74 } /* Literal.String.Doc */ 78 | .highlight .s2 { color: #e6db74 } /* Literal.String.Double */ 79 | .highlight .se { color: #ae81ff } /* Literal.String.Escape */ 80 | .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ 81 | .highlight .si { color: #e6db74 } /* Literal.String.Interpol */ 82 | .highlight .sx { color: #e6db74 } /* Literal.String.Other */ 83 | .highlight .sr { color: #e6db74 } /* Literal.String.Regex */ 84 | .highlight .s1 { color: #e6db74 } /* Literal.String.Single */ 85 | .highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ 86 | .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ 87 | .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ 88 | .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ 89 | .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ 90 | .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ 91 | 92 | .highlight .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ 93 | .highlight .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ 94 | .highlight .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ 95 | -------------------------------------------------------------------------------- /_talks/belgorod-python-2020.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Simple and complex" 4 | conference: FIT 5 | slides: https://speakerdeck.com/sobolevn/easy-and-complex 6 | youtube: 7EJ5_ONxoyg 7 | location: Belgorod, Russia 8 | language: ru 9 | date: 2020-02-08 10 | tags: python 11 | --- 12 | 13 | 14 | This talk was about code complexity. 15 | 16 | I have shown how different complexity metrics work. 17 | Even one that I have invented myself: Sobolev's complexity. 18 | I also gave a bunch of tools to fight 19 | the complexity on all levels of abstractions. 20 | 21 | This talk featured: 22 | 23 | - [`wemake-python-styleguide`](https://github.com/wemake-services/wemake-python-styleguide) 24 | - [`dry-python/returns`](https://github.com/dry-python/returns) 25 | - [Complexity Waterfall](https://sobolevn.me/2019/10/complexity-waterfall) article 26 | 27 | 28 | ## Related talks 29 | 30 | - [Automating Code Reviews](https://sobolevn.me/talks/dumpconf-2019) 31 | - [How to write python code so people would love you](https://sobolevn.me/talks/moscow-python-67-how-to-write-python-code) 32 | - [Road to SRE culture in a small company](https://sobolevn.me/talks/sbp-sre-meetup-2019) 33 | -------------------------------------------------------------------------------- /_talks/ddd-evotion-online-2020.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "DDD panel discussions" 4 | conference: DDDevotion 5 | slides: 6 | youtube: M0DyroE0TAQ 7 | location: Online 8 | language: ru 9 | date: 2020-04-02 10 | tags: ddd 11 | --- 12 | 13 | This was a panel discussion about DDD and problems it solves. 14 | 15 | I didn't have much time to speak. Only some basic stuff. 16 | But, it was still quite interesting. 17 | -------------------------------------------------------------------------------- /_talks/devconf-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Broken processes in IT: wages" 4 | conference: DevConf 5 | slides: https://speakerdeck.com/sobolevn/broken-processes-in-it-wages 6 | youtube: 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2018-05-18 10 | tags: rsdp management 11 | --- 12 | 13 | This talk was revealing the sad truth about the money we earn as employees. 14 | I tried to show all the problems in this model. 15 | 16 | It is beneficial for an employee just to earn money while doing nothing. 17 | It is beneficial for employers to pay less 18 | and force people to work as hard as possible. 19 | 20 | I have also offered them to take 21 | a look at the alternative solution: [RSDP](https://wemake-services.github.io/meta). 22 | -------------------------------------------------------------------------------- /_talks/devoops-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Introducing GitHub Actions" 4 | conference: DevOops 5 | slides: https://speakerdeck.com/sobolevn/introducing-github-actions 6 | youtube: QoCSvwkP_lQ 7 | location: St.Petersburg, Russia 8 | language: ru 9 | date: 2019-10-30 10 | tags: github devops 11 | --- 12 | 13 | This talk was an introduction into new awesome technology: GitHub Actions! 14 | 15 | I shared some personal experience and thoughts 16 | about current state of this platform and its future. 17 | 18 | It features: 19 | 20 | - 21 | - 22 | -------------------------------------------------------------------------------- /_talks/dumpconf-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Automating code reviews" 4 | conference: DUMP 5 | slides: https://speakerdeck.com/sobolevn/automating-code-reviews 6 | youtube: 17gGnTdH-R4 7 | location: Kazan, Russia 8 | language: ru 9 | date: 2019-11-08 10 | tags: rsdp management python 11 | --- 12 | 13 | This was a talk about "undoing" code reviews. 14 | The main idea is: you should concentrate on automating stuff instead of blindly following the same process over and over again. 15 | 16 | This reveals a lot of unexpected features: like code review speed and quality. 17 | 18 | 19 | ## Related talks 20 | 21 | - [Simple and complex](https://sobolevn.me/talks/belgorod-python-2020) 22 | - [How to write python code so people would love you](https://sobolevn.me/talks/moscow-python-67-how-to-write-python-code) 23 | - [Road to SRE culture in a small company](https://sobolevn.me/talks/sbp-sre-meetup-2019) 24 | -------------------------------------------------------------------------------- /_talks/elixir-lang-moscow-2016.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Moving to Elixir from the good-old Python" 4 | conference: ElixirLangMoscow 5 | slides: https://speakerdeck.com/sobolevn/pieriekhod-na-elixir-ot-privychnogho-python 6 | youtube: ZQFlAVc4zHY 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2016-09-23 10 | tags: elixir 11 | --- 12 | 13 | This talks showed a motivation behind my idea 14 | to add `elixir` to the technology stack of our company. 15 | 16 | It also covered some basic problems 17 | with `elixir` that were on-topic at that point in time. 18 | -------------------------------------------------------------------------------- /_talks/expert-fridays-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Ecto: the easy and the hard" 4 | conference: ExpertFridays 5 | slides: https://speakerdeck.com/sobolevn/ecto-the-easy-and-the-hard 6 | youtube: Blp4v3XSjPo 7 | location: Kazan, Russia 8 | language: ru 9 | date: 2018-03-02 10 | tags: elixir ecto phoenix 11 | --- 12 | 13 | Using [`ecto`](https://github.com/elixir-ecto/ecto) is great. 14 | 15 | There are different concepts that make this tool easy to use. 16 | And there are some hard abstractions. 17 | 18 | I have given an overview of its features. 19 | -------------------------------------------------------------------------------- /_talks/fpconf-2016.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Elixir propaganda" 4 | conference: FPConf 5 | slides: https://speakerdeck.com/sobolevn/elixir-propaganda 6 | youtube: SOlvn4dRoJo 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2016-12-03 10 | tags: elixir 11 | --- 12 | 13 | This talk was a rather hardcore dive into `elixir` for functional programmers. 14 | 15 | I have shown performance metrics, main ideas, 16 | and killer features of the language. 17 | -------------------------------------------------------------------------------- /_talks/fpconf-2017.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Processes. OTP. Elixir" 4 | conference: FPConf 5 | slides: https://speakerdeck.com/sobolevn/processes-otp-elixir 6 | youtube: Uzu53nnv1cw 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2017-12-17 10 | tags: elixir 11 | --- 12 | 13 | I have described all the major patterns for working with processes in `elixir`. 14 | 15 | The target audience was rather strong in the topic, 16 | so it was full of code examples and in-depth topics. 17 | -------------------------------------------------------------------------------- /_talks/funbox-code-quality-2021.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Discussion about Code Quality" 4 | conference: FunBox Meetup 5 | slides: 6 | youtube: F4yYL6F4Vvg 7 | location: Online 8 | language: ru 9 | date: 2021-09-30 10 | tags: code-quality 11 | --- 12 | -------------------------------------------------------------------------------- /_talks/github-planet-02-2022.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "How to work with large OpenSource projects" 4 | conference: GitHub Planet 5 | slides: https://speakerdeck.com/sobolevn/github-planet-opensource 6 | youtube: 7 | location: Online 8 | language: ru 9 | date: 2022-02-22 10 | tags: github 11 | --- 12 | 13 | :star: 14 | 15 | I've shared my experience about working with large OpenSource projects. 16 | Tools, practices, GitHub features. 17 | 18 | [Video link](https://youtu.be/mMfG38wj8Dg?t=462) 19 | -------------------------------------------------------------------------------- /_talks/github-planet3-2021.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "About GitHub Stars" 4 | conference: GitHub Planet 5 | slides: https://speakerdeck.com/sobolevn/about-github-stars 6 | youtube: 7 | location: Online 8 | language: ru 9 | date: 2021-03-16 10 | tags: github 11 | --- 12 | 13 | :star: 14 | 15 | This was an emotional talk about what [GitHub Stars](https://stars.github.com/) is. 16 | Why emotional? Because for me - that's the most important part of being an open-source developer! 17 | 18 | [Video link](https://youtu.be/I7DLJQyhEag?t=1142) 19 | -------------------------------------------------------------------------------- /_talks/heisenbug-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Do you test your tests?" 4 | conference: Heisenbug 5 | slides: https://speakerdeck.com/sobolevn/do-you-test-your-tests 6 | youtube: wIOX_I69zYg 7 | location: St.Petersburg, Russia 8 | language: ru 9 | date: 2019-05-17 10 | tags: testing python 11 | --- 12 | 13 | This talk is dedicated to the quality of tests. 14 | 15 | As we all know not so many developers actually care about it. 16 | This may lead to bugs and useless tests that just consume your time and CI resources. 17 | 18 | You can fight for the quality of your tests with the help of mutation tests. 19 | 20 | I have also made a [small github repo](https://github.com/sobolevn/heisenbug-2019) 21 | to show some code samples. 22 | 23 | P.S. This talk was [also presented](https://conf.python.ru/2019/abstracts/5159) 24 | on MoscowPythonConf++ the same year as a reserved one, because one of the speakers was not able to come. 25 | -------------------------------------------------------------------------------- /_talks/hexlet-online-meetup-2020.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Functional programming for beginners" 4 | conference: Hexlet meetup 5 | slides: https://speakerdeck.com/sobolevn/functional-programming-for-beginners 6 | youtube: TjDEeaohNog 7 | location: Online 8 | language: ru 9 | date: 2020-03-22 10 | tags: python 11 | --- 12 | 13 | This talk was targeted on the very beginners in programming. 14 | 15 | I was explaining how composition and types work. 16 | -------------------------------------------------------------------------------- /_talks/index-conf-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Alternative HR" 4 | conference: Index Tech 5 | slides: https://speakerdeck.com/sobolevn/alternative-hr 6 | youtube: T9VFH5Ur8w4 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2018-10-12 10 | tags: rsdp management hiring 11 | --- 12 | 13 | This talk shows that there are different ways to hire people in 2018. 14 | I have covered different things that make [`wemake.services`](https://wemake-services.github.io) 15 | so successful in this particular area. 16 | 17 | Including: 18 | - transparent and fair wages 19 | - high quality working environment 20 | - growth 21 | - clear management 22 | -------------------------------------------------------------------------------- /_talks/itea-conf-2021.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Polymorphism and Typeclasses" 4 | conference: ITeaConf 5 | slides: https://speakerdeck.com/sobolevn/polymorphism-and-typeclasses 6 | youtube: kSJpUV3WaAc 7 | location: Online 8 | language: ru 9 | date: 2021-11-14 10 | tags: python 11 | --- 12 | 13 | This talk was about Polymorphism and Typeclasses. 14 | I presented our new [`dry-python/classes`](https://github.com/dry-python/classes/) project. 15 | 16 | Based on my article ["Typeclasses in Python"](https://sobolevn.me/2021/06/typeclasses-in-python). 17 | -------------------------------------------------------------------------------- /_talks/itgm-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Introducing Pipenv" 4 | conference: ITGM 5 | slides: https://speakerdeck.com/sobolevn/introducing-pipenv 6 | youtube: 7 | location: St.Petersburg, Russia 8 | language: ru 9 | date: 2018-03-17 10 | tags: python 11 | --- 12 | 13 | There's a new awesome tool to manage your dependencies in a python world. 14 | It is called `pipenv`. And it solves so many problems! 15 | 16 | However, it is not perfect in itself. 17 | I have to guide new users through this tool. 18 | -------------------------------------------------------------------------------- /_talks/itgm-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "You got DDD wrong" 4 | conference: ITGM 5 | slides: https://speakerdeck.com/sobolevn/you-got-ddd-wrong 6 | youtube: 7 | location: St.Petersburg, Russia 8 | language: ru 9 | date: 2019-03-22 10 | tags: ddd 11 | --- 12 | 13 | Sadly, a lot of people think that `DDD` is something about 14 | new clever patterns someone else invented. 15 | 16 | My talk states that `DDD` is about your domain and your business logic. 17 | Invent any patterns you want! Or even invent your own. 18 | 19 | ## TODO 20 | 21 | Fix youtube link, when the shorter video will be available. 22 | Currently you can use [this link](https://youtu.be/Ap7oeHoccQM?t=7681) 23 | with the time code. 24 | -------------------------------------------------------------------------------- /_talks/knowledge-conf-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "How to teach programmers in the 21st century?" 4 | conference: KnowledgeConf 5 | slides: https://speakerdeck.com/sobolevn/how-to-teach-programmers-in-the-21st-century 6 | youtube: J1S05Bd2WtM 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2019-04-26 10 | tags: rsdp management 11 | --- 12 | 13 | Programmers are not motivated to share their knowledge. 14 | Programmers are not motivated to learn something new. 15 | 16 | It is fun to watch how people spend endless 17 | amount of time and money trying to teach them. 18 | 19 | This simply does not work. 20 | 21 | I have also offered them to take 22 | a look at the alternative solution: [RSDP](https://wemake-services.github.io/meta). 23 | 24 | 25 | ## Related talks 26 | 27 | - [Practical microtasks](sobolevn.me/talks/teamleadconf-2020) 28 | - [About corporate education](https://sobolevn.me/talks/teamlead-spb-meetup-2019) 29 | - [Problems in teaching programming](https://sobolevn.me/talks/softer-2018) 30 | -------------------------------------------------------------------------------- /_talks/krasnodar-dev-days-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Testing real Vue apps" 4 | conference: Krasnodar Dev Days 5 | slides: https://speakerdeck.com/sobolevn/testing-real-vue-apps 6 | youtube: ICVVCOjpCkA 7 | location: Kransodar, Russia 8 | language: ru 9 | date: 2018-09-15 10 | tags: javascript vue testing 11 | --- 12 | 13 | The main idea of this talk was to show that testing modern frontend 14 | application is easy and does not require any complicated steps. 15 | 16 | It also focuses a lot on best practices and real examples. 17 | 18 | This talk covers: 19 | - `jest` 20 | - `vue-test-utils` 21 | - mocking 22 | - creating tests fixtures 23 | - generating fake data 24 | 25 | 26 | ## Related talks 27 | 28 | - [Enterprise Ready Vue Template](https://sobolevn.me/talks/moscowjs-2018) 29 | - [Business logic in Vue.js](https://sobolevn.me/talks/moscow-vue-js-3) 30 | -------------------------------------------------------------------------------- /_talks/moscow-python-2017.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Storing secret settings in Python" 4 | conference: MoscowPython Meetup 5 | slides: https://speakerdeck.com/moscowdjango/khranieniie-siekrietnykh-nastroiek 6 | youtube: xtr3sUZUnQA 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2017-08-21 10 | tags: python django security 11 | --- 12 | 13 | Storing secrets is hard. Two people can keep a secret when one of them is dead. 14 | 15 | I have shown different strategies. 16 | And how to choose the correct one depending on your project size 17 | and the number of team members. 18 | -------------------------------------------------------------------------------- /_talks/moscow-python-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Starting a Django project the right way" 4 | conference: MoscowPython Meetup 5 | slides: https://speakerdeck.com/moscowdjango/nachinaiem-django-prilozhieniie-pravil-no 6 | youtube: vDshhQnXSfM 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2018-03-22 10 | tags: python django 11 | --- 12 | 13 | The main idea of this talk was: it is easy to start a modern web app. 14 | And we even provide the latest tech to do it: [wemake-django-template](https://github.com/wemake-services/wemake-django-template) 15 | 16 | I have covered how this tool can be helpful for all team members. 17 | 18 | I have also covered all the things that are quite important 19 | to the large-scale `django` projects. 20 | -------------------------------------------------------------------------------- /_talks/moscow-python-2021.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Announcing typed-linter" 4 | conference: MoscowPython Meetup 5 | slides: https://speakerdeck.com/sobolevn/announcing-typed-linter 6 | youtube: Q3WyANoWES8 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2021-05-24 10 | tags: python 11 | --- 12 | 13 | This was the first ever public announcement of `typed-linter`. 14 | Stay tuned to learn more! 15 | -------------------------------------------------------------------------------- /_talks/moscow-python-67-how-to-write-python-code.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "How to write python code so people would love you" 4 | conference: MoscowPython Meetup 5 | slides: https://speakerdeck.com/sobolevn/how-to-write-python-code-so-people-would-love-you-or-not 6 | youtube: ELwkO5warfs 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2019-08-21 10 | tags: python 11 | --- 12 | 13 | > so people would love you *(or not) 14 | 15 | Writing easy code is a hard thing to do. 16 | A lot of people and tools provide us 17 | a way to over-complicate our design and workflow. 18 | I suggest to take a different approach. 19 | 20 | Featuring: 21 | - 22 | - 23 | - 24 | 25 | I hope that my severe headache won't spoil the video :) 26 | 27 | 28 | ## Related talks 29 | 30 | - [Simple and complex](https://sobolevn.me/talks/belgorod-python-2020) 31 | - [Automating Code Reviews](https://sobolevn.me/talks/dumpconf-2019) 32 | - [Road to SRE culture in a small company](https://sobolevn.me/talks/sbp-sre-meetup-2019) 33 | -------------------------------------------------------------------------------- /_talks/moscow-python-conf-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Interactive holywars about linters" 4 | conference: Moscow Python Conf++ 5 | slides: https://speakerdeck.com/sobolevn/interactive-holywars-about-linters 6 | youtube: 7IVCOzL41Lk 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2018-10-22 10 | tags: python 11 | --- 12 | 13 | I wanted to show how many things are not covered by traditional `python` linters. 14 | And why it is important to have static analysis as strict as possible. 15 | 16 | This talk features [`wemake-python-styleguide`](https://github.com/wemake-services/wemake-python-styleguide). 17 | 18 | It is also made as an interactive poll where people can vote in real time. 19 | -------------------------------------------------------------------------------- /_talks/moscow-python-conf-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Do you test your tests?" 4 | conference: Moscow Python Conf++ 5 | slides: 6 | youtube: 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2019-04-05 10 | tags: python 11 | --- 12 | 13 | This was a reserved talk. One of our speakers was not able to come. 14 | So, I had to fix this situation. 15 | 16 | Consider it a repetition before [Heisenbug](https://sobolevn.me/talks/heisenbug-2019) conference. 17 | -------------------------------------------------------------------------------- /_talks/moscow-python-conf-2020.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Typing Django" 4 | conference: Moscow Python Conf++ 5 | slides: https://speakerdeck.com/sobolevn/typing-django 6 | youtube: lQZTzi_t8YI 7 | location: Online 8 | language: ru 9 | date: 2020-03-27 10 | tags: python django 11 | --- 12 | 13 | This talk was dedicated to the typing problem in Django. 14 | I have presented several tools to type your Django project. 15 | 16 | ## Links 17 | 18 | - [Types for Django](https://github.com/typeddjango/django-stubs) 19 | - [Types + Django tutorial](https://sobolevn.me/2019/08/typechecking-django-and-drf) 20 | - [Example project](https://github.com/wemake-services/wemake-django-template) 21 | -------------------------------------------------------------------------------- /_talks/moscow-python-junior-2017.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "How to learn programming, and not just Python" 4 | conference: MoscowPythonJunior 5 | slides: https://speakerdeck.com/sobolevn/how-to-learn-programming-and-not-just-python 6 | youtube: LODGssEJpNc 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2017-04-13 10 | tags: python education career 11 | --- 12 | 13 | A lot of newcomers try to learn just practical skills 14 | without building a complete knowledge system. 15 | 16 | This motivational talk tries to push junior developers to 17 | invest time and effort into self-education. 18 | 19 | And warns them, that this is a very long (never-ending) process. 20 | -------------------------------------------------------------------------------- /_talks/moscow-security-2017.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Well-known security failures" 4 | conference: MoscowSecurity 5 | slides: https://speakerdeck.com/sobolevn/well-known-security-failures 6 | youtube: 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2017-08-10 10 | tags: security 11 | --- 12 | 13 | I gave a brief overview of well-known security failures at different levels. 14 | Including small business, corporations, and government. 15 | 16 | I have also shown that it is getting worse. 17 | -------------------------------------------------------------------------------- /_talks/moscow-vue-js-3.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Business logic in Vue.js" 4 | conference: MoscowVue 5 | slides: https://speakerdeck.com/sobolevn/business-logic-in-vue-dot-js 6 | youtube: cAvX3ilAb6k 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2019-08-22 10 | tags: javascript vue 11 | --- 12 | 13 | Please, stop writing your business logic inside your components and `Vuex`. 14 | 15 | This talk explains that this is easy! 16 | 17 | Featuring: 18 | - 19 | 20 | ## Related talks 21 | 22 | - [Enterprise Ready Vue Template](https://sobolevn.me/talks/moscowjs-2018) 23 | - [Testing real Vue apps](https://sobolevn.me/talks/krasnodar-dev-days-2018) 24 | -------------------------------------------------------------------------------- /_talks/moscowjs-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Enterprise Ready Vue Template" 4 | conference: MoscowJS 5 | slides: https://speakerdeck.com/sobolevn/enterprise-ready-vue-template 6 | youtube: SXFrXbhWsVY 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2018-05-31 10 | tags: javascript vue nuxt 11 | --- 12 | 13 | The main idea of this talk was: it is easy to start a modern web app. 14 | And we even provide the latest tech to do it: [wemake-vue-template](https://github.com/wemake-services/wemake-vue-template) 15 | 16 | I have also covered all the things that are quite important 17 | to the large-scale Vue projects, including: 18 | 19 | - documentation 20 | - typing, linting, and testing 21 | - SSR and business logic 22 | 23 | ## Related talks 24 | 25 | - [Testing real Vue apps](https://sobolevn.me/talks/krasnodar-dev-days-2018) 26 | - [Business logic in Vue.js](https://sobolevn.me/talks/moscow-vue-js-3) 27 | 28 | -------------------------------------------------------------------------------- /_talks/piter-a11y-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Automating the a11y checks" 4 | conference: piter-a11y 5 | slides: https://speakerdeck.com/sobolevn/automating-a11y-checks 6 | youtube: T_-xurFzyoc 7 | location: St.Petersburg, Russia 8 | language: ru 9 | date: 2019-09-25 10 | tags: a11y javascript css 11 | --- 12 | 13 | The main idea of this talk was: a11y covers more cases that people are usually told. 14 | I also made it clear that a11y and SRE is almost the same thing. But from different angles. 15 | 16 | I have also featured our `Vue` template with all a11y features enabled and checked: [wemake-vue-template](https://github.com/wemake-services/wemake-vue-template) 17 | 18 | -------------------------------------------------------------------------------- /_talks/podlodka-backend-crew-2021.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Discussion about OpenSource" 4 | conference: podlodka backend crew 5 | slides: 6 | youtube: JeCzGdaA37g 7 | location: Online 8 | language: ru 9 | date: 2021-10-20 10 | tags: opensource 11 | --- 12 | -------------------------------------------------------------------------------- /_talks/pycon-russia-2021.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Typed static analysis in Python" 4 | conference: PyCon Ru 5 | slides: https://speakerdeck.com/sobolevn/typed-static-analysis-in-python 6 | youtube: VlczfnJujUs 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2021-09-05 10 | tags: python 11 | --- 12 | -------------------------------------------------------------------------------- /_talks/python-barnaul-2021.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Problems of static analysis in Python" 4 | conference: PythonBarnaul 5 | slides: https://speakerdeck.com/sobolevn/problems-of-static-analysis-in-python 6 | youtube: 7 | location: Online 8 | language: ru 9 | date: 2021-04-22 10 | tags: python mypy flake8 11 | --- 12 | 13 | This was a really short and high-level talk about problems in static analysis in Python. 14 | 15 | It was an introductionary talk to student-audience. 16 | -------------------------------------------------------------------------------- /_talks/rails-club-2017.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Elixir is not Ruby, it is better" 4 | conference: RailsClub 5 | slides: https://speakerdeck.com/sobolevn/elixir-is-not-ruby-it-is-better 6 | youtube: 88Kp-HQ6LX4 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2017-09-23 10 | tags: elixir ruby 11 | --- 12 | 13 | When speaking about `elixir` among `ruby` developers you have to understand 14 | the main idea: these languages are not the same. 15 | 16 | Even if the syntax looks familiar. They are completely different. 17 | And I have shown that `elixir` solves a lot of the problems we have in `ruby`. 18 | -------------------------------------------------------------------------------- /_talks/rit-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Preaching about blameless environment" 4 | conference: RIT++ 5 | slides: https://speakerdeck.com/sobolevn/preaching-about-blameless-environment 6 | youtube: ajyR6YXLpRQ 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2019-05-27 10 | tags: rsdp 11 | --- 12 | 13 | This was not a talk. It was a preaching. 14 | 15 | Do not expect any technical details here. 16 | 17 | You either like this preaching, or hate it. 18 | There's nothing in between. 19 | 20 | 21 | ## Related talks 22 | 23 | - [Practical microtasks](sobolevn.me/talks/teamleadconf-2020) 24 | - [What is quality?](https://sobolevn.me/talks/rit-quality-conf-2019) 25 | - [How to teach programmers in the 21st century?](https://sobolevn.me/talks/knowledge-conf-2019) 26 | - [Alternative HR](https://sobolevn.me/talks/index-conf-2018) 27 | -------------------------------------------------------------------------------- /_talks/rit-quality-conf-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Open discussion: what is quality?" 4 | conference: RIT++ 5 | url: https://ritfest.ru/2019/abstracts/5369 6 | date: 2019-05-27 7 | location: Moscow, Russia 8 | slides: 9 | youtube: 038HUCSr-rs 10 | language: ru 11 | tags: rsdp 12 | --- 13 | 14 | This was an open discussion about the quality of code in your project. 15 | I shared an opinion about my view on code's quality. 16 | 17 | My opinion was: 18 | 19 | > The most important property of software quality: is software (code) quality! 20 | 21 | 22 | ## Related talks 23 | 24 | - [Simple and complex](https://sobolevn.me/talks/belgorod-python-2020) 25 | - [Practical microtasks](sobolevn.me/talks/teamleadconf-2020) 26 | - [Preaching about blameless environment](https://sobolevn.me/talks/rit-2019) 27 | - [How to teach programmers in the 21st century?](https://sobolevn.me/talks/knowledge-conf-2019) 28 | -------------------------------------------------------------------------------- /_talks/russia-opensource-meetup-2021.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "OpenSource Motivation" 4 | conference: Russia OpenSource 5 | slides: 6 | youtube: FxZ9t8aQlQg 7 | location: Online 8 | language: ru 9 | date: 2021-09-22 10 | tags: opensource 11 | --- 12 | 13 | https://rb.ru/news/russia-open-source-meetup/ 14 | -------------------------------------------------------------------------------- /_talks/russian-python-week-open-source.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Monetizing Open Source" 4 | conference: Russian Python Week 5 | slides: 6 | youtube: mkTLBZ-cFOE 7 | location: Online 8 | language: ru 9 | date: 2020-09-15 10 | tags: open-source 11 | --- 12 | 13 | I have shared some experience and ideas on how one can monetize open-source projects. 14 | The short answer is: consulting. 15 | 16 | Do you want to talk about it? Reach me via [DryLabs](https://drylabs.io) 17 | -------------------------------------------------------------------------------- /_talks/saint-apps-conf-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Server languages for mobile devs" 4 | conference: Saint Apps Conf 5 | slides: https://speakerdeck.com/sobolevn/server-languages-for-mobile-devs 6 | youtube: JTk7dYxELIg 7 | location: St.Petersburg, Russia 8 | language: ru 9 | date: 2019-10-22 10 | tags: python cs 11 | --- 12 | 13 | In this talk I gave my opinion on more than 10 different server-side programming languages. 14 | 15 | This talk was an intro one for mobile devs 16 | who are unfamiliar with the server-side programming. 17 | -------------------------------------------------------------------------------- /_talks/saint-prug-2017.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Elixir: is it good? When to use?" 4 | conference: SaintPRUG 5 | slides: https://speakerdeck.com/sobolevn/elixir-is-it-good-when-to-use 6 | youtube: ujC-Sea-JLM 7 | location: St.Petersburg, Russia 8 | language: ru 9 | date: 2017-11-23 10 | tags: elixir ruby 11 | --- 12 | 13 | A lot of `ruby` developers seem to be interested in `elixir`. 14 | 15 | So, I have described when to use `elixir`, and when to use it. 16 | I have also covered its advantages and disadvantages. 17 | -------------------------------------------------------------------------------- /_talks/sbp-sre-meetup-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Road to SRE culture in a small company" 4 | conference: SPB Reliability Meetup 5 | slides: https://speakerdeck.com/sobolevn/road-to-sre-culture-in-a-small-company 6 | youtube: rAP80jbzfg8 7 | location: St.Petersburg, Russia 8 | language: ru 9 | date: 2019-02-28 10 | tags: python sre devops 11 | --- 12 | 13 | We do not have SRE culture in [wemake.services](https://wemake-services.github.io). 14 | And we somehow manage to live without it. A lot of people ask about it. 15 | 16 | This talk covers a lot of tools and practices 17 | that we actively use to decrease the negative effect of lack of SRE. 18 | 19 | And it also raises an important question to think about: 20 | 21 | > How to combine RSPD and SRE together in a single business process? 22 | 23 | ## Related talks 24 | 25 | - [Simple and complex](https://sobolevn.me/talks/belgorod-python-2020) 26 | - [Automating Code Reviews](https://sobolevn.me/talks/dumpconf-2019) 27 | - [How to write python code so people would love you](https://sobolevn.me/talks/moscow-python-67-how-to-write-python-code) 28 | -------------------------------------------------------------------------------- /_talks/softer-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Problems in teaching programming" 4 | conference: Softer 5 | slides: https://speakerdeck.com/sobolevn/problems-in-teaching-programming 6 | youtube: 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2018-01-30 10 | tags: education career 11 | --- 12 | 13 | I teach software developers for more than 3 years at the time of the talk. 14 | 15 | I had different experiences with universities, courses, and mentorship. 16 | I have tried to sum it all up. 17 | 18 | Since, there are different problems in this field 19 | which are generally not covered at all. 20 | 21 | 22 | ## Related talks 23 | 24 | - [How to teach programmers in the 21st century?](https://sobolevn.me/talks/knowledge-conf-2019) 25 | - [About corporate education](https://sobolevn.me/talks/teamlead-spb-meetup-2019) 26 | -------------------------------------------------------------------------------- /_talks/teamlead-spb-meetup-2019.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "About corporate education" 4 | conference: TeamLead SPB 5 | slides: https://speakerdeck.com/sobolevn/about-corporate-education 6 | youtube: 7 | location: St.Petersburg, Russia 8 | language: ru 9 | date: 2019-10-10 10 | tags: rsdp management education 11 | --- 12 | 13 | The main idea of this talk was: there are different methods of corporate education. And you should choose wisely. 14 | These methods include [audits](https://wemake-services.github.io/meta/rsdp/audits/) and [corporate consulting](https://drylabs.io). 15 | 16 | I also discussed why online courses do not work and what problems other platforms have. 17 | 18 | There are no videos yet, but here's the [online translation](https://www.youtube.com/watch?v=J65mOFXTpNc). 19 | 20 | ## Related talks 21 | 22 | - [How to teach programmers in the 21st century?](https://sobolevn.me/talks/knowledge-conf-2019) 23 | - [Problems in teaching programming](https://sobolevn.me/talks/softer-2018) 24 | -------------------------------------------------------------------------------- /_talks/teamleadconf-2020.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Practical microtasks" 4 | conference: TeamLeadConf 5 | slides: https://speakerdeck.com/sobolevn/practical-microtasks 6 | youtube: 7wgxJirrg8U 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2020-02-10 10 | tags: rsdp management 11 | --- 12 | 13 | This talk is a practical guide to microtasks. 14 | 15 | It demistifies a lot of rumors, misconceptions, 16 | and shows real problems about them. 17 | 18 | Here's a short text version of my talk in Russian: [vc.ru](https://vc.ru/ontico/106882-chto-kak-i-zachem-delat-timlidu) 19 | 20 | 21 | ## Related talks 22 | 23 | - [Simple and complex](https://sobolevn.me/talks/belgorod-python-2020) 24 | - [Automating code review](https://sobolevn.me/talks/dumpconf-2019) 25 | - [Preaching about blameless environment](https://sobolevn.me/talks/rit-2019) 26 | - [How to teach programmers in the 21st century?](https://sobolevn.me/talks/knowledge-conf-2019) 27 | - [Alternative HR](https://sobolevn.me/talks/index-conf-2018) 28 | -------------------------------------------------------------------------------- /_talks/vladimir-tech-talks-2021.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "New GitHub Features" 4 | conference: Vladimir Tech Talks 5 | slides: https://speakerdeck.com/sobolevn/new-github-features 6 | youtube: NQ4iXMzkuOg 7 | location: Vladimir, Russia 8 | language: ru 9 | date: 2021-04-26 10 | tags: github 11 | --- 12 | -------------------------------------------------------------------------------- /_talks/write-the-docs-2018.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: talk 3 | title: "Continuous Documentation" 4 | conference: Write The Docs Moscow 5 | slides: https://speakerdeck.com/sobolevn/continous-documentation 6 | youtube: UMEDdvUNRAQ 7 | location: Moscow, Russia 8 | language: ru 9 | date: 2018-08-22 10 | tags: rsdp documentation 11 | --- 12 | 13 | This talk covers [a simple process](https://wemake-services.github.io/meta/) 14 | we follow in our company to document the code we write. 15 | 16 | The main idea of the process is to: 17 | 18 | - refuse to work by creating bug reports until task is clear 19 | - pay for found bugs in documentation 20 | - restrict informal communication 21 | 22 | It also mentions other processes we have. 23 | -------------------------------------------------------------------------------- /assets/images/favicons/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/android-icon-144x144.png -------------------------------------------------------------------------------- /assets/images/favicons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/android-icon-192x192.png -------------------------------------------------------------------------------- /assets/images/favicons/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/android-icon-36x36.png -------------------------------------------------------------------------------- /assets/images/favicons/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/android-icon-48x48.png -------------------------------------------------------------------------------- /assets/images/favicons/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/android-icon-72x72.png -------------------------------------------------------------------------------- /assets/images/favicons/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/android-icon-96x96.png -------------------------------------------------------------------------------- /assets/images/favicons/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/apple-icon-114x114.png -------------------------------------------------------------------------------- /assets/images/favicons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/apple-icon-120x120.png -------------------------------------------------------------------------------- /assets/images/favicons/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/apple-icon-144x144.png -------------------------------------------------------------------------------- /assets/images/favicons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/apple-icon-152x152.png -------------------------------------------------------------------------------- /assets/images/favicons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/apple-icon-180x180.png -------------------------------------------------------------------------------- /assets/images/favicons/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/apple-icon-57x57.png -------------------------------------------------------------------------------- /assets/images/favicons/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/apple-icon-60x60.png -------------------------------------------------------------------------------- /assets/images/favicons/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/apple-icon-72x72.png -------------------------------------------------------------------------------- /assets/images/favicons/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/apple-icon-76x76.png -------------------------------------------------------------------------------- /assets/images/favicons/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/apple-icon-precomposed.png -------------------------------------------------------------------------------- /assets/images/favicons/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/apple-icon.png -------------------------------------------------------------------------------- /assets/images/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /assets/images/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /assets/images/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /assets/images/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /assets/images/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/favicon.ico -------------------------------------------------------------------------------- /assets/images/favicons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /assets/images/favicons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/ms-icon-144x144.png -------------------------------------------------------------------------------- /assets/images/favicons/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/ms-icon-150x150.png -------------------------------------------------------------------------------- /assets/images/favicons/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/ms-icon-310x310.png -------------------------------------------------------------------------------- /assets/images/favicons/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sobolevn/sobolevn.github.io/effef3b80b3bf94ba20feb2be9238ae3aa989259/assets/images/favicons/ms-icon-70x70.png -------------------------------------------------------------------------------- /css/main.scss: -------------------------------------------------------------------------------- 1 | --- 2 | # Main scss file 3 | --- 4 | @charset "utf-8"; 5 | 6 | // Helpers 7 | @import 8 | 'helpers/mixins', 9 | 'helpers/variables'; 10 | 11 | // Base 12 | @import 13 | 'base/reset'; 14 | 15 | // Utilities 16 | @import 17 | 'utilities/layout', 18 | 'utilities/separator'; 19 | 20 | // Components 21 | @import 22 | 'components/page', 23 | 'components/article', 24 | 'components/tag', 25 | 'components/archives'; 26 | 27 | // Vendor 28 | @import 29 | 'vendor/highlight'; 30 | 31 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 |
5 | 6 | {% for post in site.posts %} 7 | {% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %} 8 | {% capture next_year %}{{ post.previous.date | date: "%Y" }}{% endcapture %} 9 | 10 | {% if forloop.first %} 11 |

{{ this_year }}

12 |
    13 | {% endif %} 14 |
  • 15 |

    16 | {{ post.title }} 17 |

    18 |

    19 | {{ post.date | date: "%B %-d, %Y" }} 20 | {% include read_time.html post=post %} 21 | 22 | 24 | 0 comments 25 | 26 | 27 |

    28 |

    {{ post.description }}

    29 |
  • 30 | {% if forloop.last %} 31 |
32 | {% else %} 33 | {% if this_year != next_year %} 34 | 35 | 36 |

37 | {{ next_year }} 38 |

39 |
    40 | {% endif %} 41 | {% endif %} 42 | {% endfor %} 43 |
44 | 45 | 48 | -------------------------------------------------------------------------------- /opensearch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | sobolevn 4 | sobolevn's personal blog 5 | python javascript rsdp elixir 6 | mail@sobolevn.me 7 | 8 | On good software, mediocre software, and bad software. Also rants about management and development processes. 9 | https://sobolevn.me/assets/images/favicons/favicon-96x96.png 10 | Nikita Sobolev 11 | Copyright 2016, Nikita Sobolev, All Rights Reserved 12 | open 13 | false 14 | en-us 15 | UTF-8 16 | UTF-8 17 | 18 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | Sitemap: https://sobolevn.me/sitemap.xml 2 | 3 | User-agent: * 4 | Disallow: /404.html 5 | --------------------------------------------------------------------------------