├── .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 | 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: '{{ site.title }}
9 | 10 | 14 | 15 | {% include read_time.html post=page %} 16 | Start a discussion 17 | Edit this page 18 |
19 |9 | 10 | 14 | 15 | {{ page.conference }} 16 | {{ page.location }} 17 | Edit this page 18 |
19 |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 |36 | {{ activity.date | date: "%b %-d, %Y" }} 37 | 38 | {% assign domain = activity.link | replace_first: 'www.', '' | split: '/' %} 39 | {{ domain[2] }}, {{ activity.type | downcase }} :{{ activity.language }}: 40 | 41 |
42 |
10 | I love conference and meetups. I generally speak about python
,
11 | elixir
, javascript
, and management.
12 |
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 |34 | {{ talk.date | date: "%b %-d, %Y" }} 35 | 36 | {{ talk.conference }}, {{ talk.location }} :{{ talk.language }}: 37 | 38 |
39 |59 | {{ talk.date | date: "%b %-d, %Y" }} 60 | 61 | {{ talk.conference }}, {{ talk.location }} :{{ talk.language }}: 62 | 63 |
64 |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 |  61 | 62 | ## Spectacle 63 | 64 | [Spectacle](https://www.spectacleapp.com/) allows using keyboard shortcuts to reorganize windows on your desktop. 65 | 66 |  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 |  80 | - Allows to view image sizes 81 |  82 | - Highlights, pretty-prints, and transforms `json` 83 |  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 |  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 |  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 |  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 | -Announcing my latest Mac app:
— Sindre Sorhus (@sindresorhus) 14 июля 2019 г.
Dato—A better menu bar clock with calendar and time zones.https://t.co/Div5ozziJb pic.twitter.com/xRPutTfDkt
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 |