├── .gitignore ├── README.md ├── _images ├── 3nf-violation.png ├── aws-innovate-ai-ml-21-1.png ├── aws-innovate-ai-ml-21-2.png ├── aws-innovate-ai-ml-21-3.png ├── aws-innovate-ai-ml-21-4.png ├── aws-innovate-ai-ml-21-5.png ├── aws-innovate-ai-ml-21-6.png ├── fast-ai-1.png ├── patterns-architecture-cqrs-martin-fowler.png ├── patterns-architecture-event-sourcing-overview.png ├── pycon-2022-apis.jpeg ├── pycon-2022-monolith.jpeg ├── pycon-2022-music-recommendations.jpeg ├── pycon-2022-music-tagging.jpeg ├── pycon-2022-source-separation-1.jpeg ├── pycon-2022-source-separation.jpeg └── pycon-2022-transcription.jpeg ├── books ├── architecture-hard-parts.md ├── build.md ├── clean-agile.md ├── clean-code.md ├── coaching-agile-teams.md ├── code-complete.md ├── comic-agile.md ├── cracking-coding-interview │ ├── Dockerfile │ ├── docker-compose.yml │ ├── notes.md │ ├── requirements.txt │ └── src │ │ ├── ch01_arrays_and_strings │ │ ├── check_permutation.py │ │ ├── is_unique.py │ │ ├── one_away.py │ │ ├── palindrome_permutation.py │ │ ├── rotate_matrix.py │ │ ├── string_compression.py │ │ ├── string_rotation.py │ │ ├── urlify.py │ │ └── zero_matrix.py │ │ └── ch02_linked_lists │ │ ├── delete_middle_node.py │ │ ├── intersection.py │ │ ├── linked_list.py │ │ ├── loop_detection.py │ │ ├── palindrome.py │ │ ├── partition.py │ │ ├── remove_dups.py │ │ ├── return_kth_to_last.py │ │ └── sum_lists.py ├── ddd.md ├── ddia.md ├── docker-deep-dive.md ├── elixir.md ├── fundamentals-of-architecture.md ├── go │ ├── ch01 │ │ ├── Makefile │ │ └── hello.go │ ├── ch02 │ │ ├── const.go │ │ └── unicode.go │ ├── ch03 │ │ └── types.go │ ├── ch04 │ │ ├── case.go │ │ ├── for.go │ │ └── if.go │ ├── ch05 │ │ ├── anonymous.go │ │ ├── deferExample.go │ │ ├── functionAsParam.go │ │ ├── functions.go │ │ ├── functionsAreValues.go │ │ └── returnFunction.go │ ├── ch06 │ │ └── pointers.go │ ├── ch07 │ │ ├── counter.go │ │ ├── dependencyInjection.go │ │ ├── embedding.go │ │ ├── intTree.go │ │ ├── interfaces.go │ │ ├── iota.go │ │ └── types.go │ ├── ch08 │ │ ├── customErrors.go │ │ ├── errors.go │ │ ├── panic.go │ │ ├── recover.go │ │ ├── sentinel.go │ │ └── wrappingErrors.go │ ├── ch09 │ │ ├── formatter │ │ │ └── formatter.go │ │ ├── main.go │ │ └── math │ │ │ └── math.go │ ├── ch10 │ │ ├── deadlock.go │ │ ├── deadlockSolution.go │ │ └── goroutinesExample.go │ └── notes.md ├── hands-on-ml.md ├── head-first-design-patterns │ ├── ch_01_strategy.py │ ├── ch_02_observer.py │ ├── ch_03_decorator.py │ ├── ch_04_factory.py │ ├── ch_05_singleton.py │ ├── ch_06_command.py │ ├── ch_07_adapter.py │ ├── ch_07_facade.py │ ├── ch_08_template_method.py │ ├── ch_09_composite.py │ ├── ch_09_iterator.py │ ├── ch_10_state.py │ ├── ch_11_virtual_proxy.py │ └── notes.md ├── kubernetes-book.md ├── kubernetes-in-action.md ├── nlp-book.md ├── peopleware.md ├── pragmatic-programmer.md ├── pytest │ ├── .coveragerc │ ├── Dockerfile │ ├── docker-compose.yml │ ├── notes.md │ ├── requirements.txt │ ├── setup.cfg │ ├── src │ │ ├── __init__.py │ │ ├── api.py │ │ ├── cli.py │ │ └── db.py │ └── tests │ │ ├── ch_02 │ │ ├── test_card.py │ │ ├── test_classes.py │ │ ├── test_exceptions.py │ │ └── test_helper.py │ │ ├── ch_03 │ │ ├── conftest.py │ │ ├── test_autouse.py │ │ ├── test_count.py │ │ ├── test_count_initial.py │ │ ├── test_fixtures.py │ │ ├── test_rename_fixture.py │ │ └── test_some.py │ │ ├── ch_04 │ │ ├── conftest.py │ │ ├── test_config.py │ │ ├── test_tmp.py │ │ └── test_version.py │ │ ├── ch_05 │ │ └── test_parametrize.py │ │ ├── ch_06 │ │ ├── pytest.ini │ │ ├── test_builtin.py │ │ ├── test_custom.py │ │ └── text_combination.py │ │ ├── ch_12 │ │ ├── hello.py │ │ └── test_hello.py │ │ └── ch_15 │ │ ├── conftest.py │ │ ├── pytest.ini │ │ └── test_slow.py ├── python-architecture-patterns │ ├── Dockerfile │ ├── Makefile │ ├── docker-compose.yml │ ├── notes.md │ ├── requirements.txt │ ├── setup.cfg │ ├── src │ │ ├── __init__.py │ │ ├── adapters │ │ │ ├── __init__.py │ │ │ ├── notifications.py │ │ │ ├── orm.py │ │ │ ├── redis_publisher.py │ │ │ └── repository.py │ │ ├── app.py │ │ ├── bootstrap.py │ │ ├── config.py │ │ ├── domain │ │ │ ├── __init__.py │ │ │ ├── commands.py │ │ │ ├── events.py │ │ │ └── model.py │ │ ├── redis_consumer.py │ │ ├── service_layer │ │ │ ├── __init__.py │ │ │ ├── handlers.py │ │ │ ├── message_bus.py │ │ │ └── unit_of_work.py │ │ └── views.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── e2e │ │ ├── __init__.py │ │ ├── api_client.py │ │ ├── redis_client.py │ │ ├── test_app.py │ │ └── test_external_events.py │ │ ├── integration │ │ ├── __init__.py │ │ ├── test_uow.py │ │ └── test_views.py │ │ └── unit │ │ ├── __init__.py │ │ ├── test_batches.py │ │ ├── test_handlers.py │ │ └── test_product.py ├── refactoring.md ├── release-it.md ├── system-design-interview.md ├── tidy-first.md └── understanding-distributed-systems.md ├── case-studies └── reddit.md ├── conferences ├── aws-innovate-ai-ml-21.md ├── brown-bags.md └── pycon-2022.md ├── courses └── fast-ai.md ├── patterns ├── abbreviations.md └── architecture.md └── teaching ├── python-intermediate ├── README.md └── presentation.pdf └── python-intro ├── README.md ├── notebook.ipynb └── presentation.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .AppleDouble 3 | .LSOverride 4 | .idea 5 | .ipynb_checkpoints 6 | */.pytest_cache/ 7 | git-user.sh 8 | /excluded_resources/* 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 👉👉👉 Visit [musicat.fm](https://musicat.fm) 😻 2 | 3 | You can connect Spotify and Apple Music to it to discover many cool statistics about your taste! 4 | 5 | (I'm the author 🤩) 6 | 7 | --- 8 | 9 | ## Notes 10 | 11 | ### Books 12 | 13 | 👀 In progress: 14 | 15 | - [System design interview](books/system-design-interview.md) 16 | 17 | #### ✅ Finished: 18 | 19 | - Code: 20 | - [Clean Code: A Handbook of Agile Software Craftsmanship](books/clean-code.md) 21 | - [Learning Go: An Idiomatic Approach to Real-World Go Programming](books/go/notes.md) 22 | - [Python Testing with Pytest](books/pytest/notes.md) 23 | - [Refactoring: Improving the Design of Existing Code](books/refactoring.md) 24 | - [Tidy first?](books/tidy-first.md) 25 | 26 | - Architecture: 27 | - [Architecture Patterns with Python](books/python-architecture-patterns/notes.md) 28 | - [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](books/ddia.md) 29 | - [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](books/head-first-design-patterns/notes.md) 30 | - [Release It! Design and Deploy Production-Ready Software](books/release-it.md) 31 | - [Fundamentals of Software Architecture](books/fundamentals-of-architecture.md) 32 | 33 | - Process: 34 | - [Clean Agile: Back to Basics](books/clean-agile.md) 35 | - [Domain-Driven Design: Tackling Complexity in the Heart of Software](books/ddd.md) 36 | - [Peopleware: Productive Projects and Teams](books/peopleware.md) 37 | - [The Pragmatic Programmer](books/pragmatic-programmer.md) 38 | - [Comic Agilé](books/comic-agile.md) 39 | 40 | - DevOps: 41 | - [The Kubernetes Book](books/kubernetes-book.md) 42 | 43 | - Product: 44 | - :eyes: 45 | 46 | - ML: 47 | - [Speech and Language Processing: An Introduction to Natural Language Processing, Computational Linguistics and Speech Recognition](books/nlp-book.md) 48 | 49 | #### ☑️ Finished partially: 50 | 51 | - [Code Complete: A Practical Handbook of Software Construction](books/code-complete.md) 52 | - [Cracking the Coding Interview](books/cracking-coding-interview/notes.md) 53 | - [Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow: Concepts, Tools, and Techniques to Build Intelligent Systems](books/hands-on-ml.md) 54 | - [Build](books/build.md) 55 | - [Coaching Agile Teams](books/coaching-agile-teams.md) 56 | 57 | #### ⏳ Queue: 58 | 59 | - [Docker Deep Dive](books/docker-deep-dive.md) 60 | - [Software Architecture: The Hard Parts](books/architecture-hard-parts.md) 61 | - [Understanding Distributed Systems](books/understanding-distributed-systems.md) 62 | - [Kubernetes in Action](books/kubernetes-in-action.md) 63 | - [Elixir in Action](books/elixir.md) 64 | 65 | ### Case Studies 66 | 67 | - [Reddit](case-studies/reddit.md) 68 | 69 | ### Conferences 70 | 71 | - [PyCon 2022](conferences/pycon-2022.md) 72 | - [AWS Innovate: AI/ML Edition 2021](conferences/aws-innovate-ai-ml-21.md) 73 | - [Brown Bags](conferences/brown-bags.md) 74 | 75 | ### Patterns 76 | 77 | - [Abbreviations](patterns/abbreviations.md) 78 | - [Architecture](patterns/architecture.md) 79 | 80 | ### Teaching 81 | 82 | - [Introduction to Programming: Python for beginners](teaching/python-intro) 83 | - [Python Intermediate](teaching/python-intermediate) 84 | 85 | ### Courses 86 | 87 | - [Course @ FastAI](courses/fast-ai.md) 88 | -------------------------------------------------------------------------------- /_images/3nf-violation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/3nf-violation.png -------------------------------------------------------------------------------- /_images/aws-innovate-ai-ml-21-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/aws-innovate-ai-ml-21-1.png -------------------------------------------------------------------------------- /_images/aws-innovate-ai-ml-21-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/aws-innovate-ai-ml-21-2.png -------------------------------------------------------------------------------- /_images/aws-innovate-ai-ml-21-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/aws-innovate-ai-ml-21-3.png -------------------------------------------------------------------------------- /_images/aws-innovate-ai-ml-21-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/aws-innovate-ai-ml-21-4.png -------------------------------------------------------------------------------- /_images/aws-innovate-ai-ml-21-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/aws-innovate-ai-ml-21-5.png -------------------------------------------------------------------------------- /_images/aws-innovate-ai-ml-21-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/aws-innovate-ai-ml-21-6.png -------------------------------------------------------------------------------- /_images/fast-ai-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/fast-ai-1.png -------------------------------------------------------------------------------- /_images/patterns-architecture-cqrs-martin-fowler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/patterns-architecture-cqrs-martin-fowler.png -------------------------------------------------------------------------------- /_images/patterns-architecture-event-sourcing-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/patterns-architecture-event-sourcing-overview.png -------------------------------------------------------------------------------- /_images/pycon-2022-apis.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/pycon-2022-apis.jpeg -------------------------------------------------------------------------------- /_images/pycon-2022-monolith.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/pycon-2022-monolith.jpeg -------------------------------------------------------------------------------- /_images/pycon-2022-music-recommendations.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/pycon-2022-music-recommendations.jpeg -------------------------------------------------------------------------------- /_images/pycon-2022-music-tagging.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/pycon-2022-music-tagging.jpeg -------------------------------------------------------------------------------- /_images/pycon-2022-source-separation-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/pycon-2022-source-separation-1.jpeg -------------------------------------------------------------------------------- /_images/pycon-2022-source-separation.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/pycon-2022-source-separation.jpeg -------------------------------------------------------------------------------- /_images/pycon-2022-transcription.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/_images/pycon-2022-transcription.jpeg -------------------------------------------------------------------------------- /books/architecture-hard-parts.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Software Architecture: The Hard Parts: Modern Tradeoff Analysis for Distributed Architectures 4 | 5 | Book by Pramod Sadalage, Neal Ford, Mark Richards, Zhamak Dehghani 6 | -------------------------------------------------------------------------------- /books/coaching-agile-teams.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Coaching Agile Teams 4 | 5 | Book by Lyssa Adkins 6 | 7 | - [1. Will I be a Good Coach?](#1-will-i-be-a-good-coach) 8 | 9 | ## 1. Will I be a Good Coach? 10 | 11 | If teams are to have kinds of stellar experiences, leverage agile to teh full competitive advantage it was meant to 12 | provide. 13 | 14 | Agile coaching matters because it helps both, producing products that matter in the real, complex and uncertain world, 15 | and adding meaning to people's work lives. 16 | 17 | Agile is easy to get going yet hard to do well. 18 | 19 | Imagine a team that admits mistakes, reinforces their shared values, forgives one another, and moves on. Do you think 20 | such a team would come up with astonishing ideas? 21 | 22 | An agile (or Scrum) coach is: 23 | 24 | - someone who appreciates teh depths of agile practices and principles and can help teams appreciate them too 25 | - someone who has faces big dragons, organizational impediments, and has become a coach to managers and other outsiders 26 | in the course of addressing them 27 | - someone who can help management at all levels of the organization the benefits of working agile 28 | - someone who has brought ideas from professional facilitation, coaching, conflict management, mediation, theater and 29 | more to help the team become a high-performance team 30 | 31 | Native wiring for coaching: 32 | 33 | - ability to "read a room", ability to read emotion in the air and know whether all is good 34 | - care about people more than products 35 | - cultivate curiosity 36 | - believe that people are basically good 37 | - they know that plans fall apart, so they act in the moment with the team 38 | - any group of people can do good things 39 | - it drives them crazy when someone says "yeah, I know, it's a waste of time, but that's how we do it here" 40 | - chaos and destruction are simply building blocks for something better 41 | - they risk being wrong 42 | -------------------------------------------------------------------------------- /books/comic-agile.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Comic Agilé 4 | 5 | Book by Luxshan Ratnaravi, Mikkel Noe-Nygaard 6 | 7 | - [1: Transformation](#1-transformation) 8 | - [4: Team](#4-team) 9 | - [6: Miscellaneous](#6-miscellaneous) 10 | 11 | ## 1: Transformation 12 | 13 | Instead of taking a waterfall approach to your agile transformation, take an iterative one and grow the scope 14 | organically. Focus on changing the organizational culture to align with an agile one. 15 | 16 | Product Owners don't dictate anything just because they are accountable for maximizing the value through effective 17 | product backlog. The entire Scrum Team collaborates on creating a plan for the next Sprint. 18 | 19 | Asses the psychological safety in your organization. If it is too low, seek to make working agreements where blameless 20 | post-mortems are part of them, so you can create a culture of promoting healthy conflicts and celebration of mistakes ( 21 | and learning from them). Help your managers in demanding more psychological safety from their superiors, as that is a 22 | prerequisite for the managers creating it for you. 23 | 24 | If you only partly adopted the agile way of working, the scope and time might be fixed, so the only parameter that the 25 | teams can rally vary is how much technical debt to create. 26 | 27 | ## 4: Team 28 | 29 | Team Velocity - the velocity is only for the team. If Management doesn't get that, educate them on the purpose and 30 | nature of velocity. 31 | 32 | Technical Debt - If you PO doesn't get the importance of reducing Technical Debt, you need to educate them - spending 33 | some time now on reducing the technical debt will most likely decrease time-to-market of new features. 34 | 35 | Avoid external participants in the team's retrospective (lack of trust to the externals). 36 | 37 | Use a simple tools for building agile culture, by taking a just-enough approach to your tooling, you free ip energy to 38 | focus on the needed behavioral changes. 39 | 40 | DevOps is not just about tools, testing and CI/CD pipelines - it is more about culture, breaking down silos and 41 | aligning cross-functional teams tp the paths of value delivery. 42 | 43 | WIP limit should create a pull system in the team's flow. This should then bring a conversation about collaboration and 44 | the knowledge sharing needed to ensure that the entire team can actually swarm around each PBI. 45 | 46 | Mob Programming - is about working collaboratively in groups of +3 to deliver high quality software and/or share 47 | knowledge between the developers in the mob. The Driver - controls the keyboard, the Navigators are thinking, 48 | discussing, reviewing and reflecting. The roles are interchanged. 49 | 50 | Stability is the foundation for building the trust needed to become high-preforming teams. If team keeps changing, they 51 | will have difficulties moving up Tucksman's phases - forming, storming, norming, performing. 52 | 53 | ## 6: Miscellaneous 54 | 55 | In the spirit of openness, you don't have to wait for the Retrospective to bring up potential improvements to your ways 56 | of working. 57 | 58 | Companies with diverse leadership are 45% more likely to grow their market share and 70% more likely to capture new 59 | markets compared to companies with "non-diverse" leadership. Behavioral diversity is the other half of the equation, 60 | which includes: 61 | 62 | - ensuring everyone is heard 63 | - making it safe to propose novel ideas 64 | - giving team members decision-making authority 65 | - sharing credit for success 66 | - giving actionable feedback 67 | - implementing feedback from the team 68 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10.4 2 | 3 | WORKDIR /src 4 | 5 | ENV PYTHONPATH "${PYTHONPATH}:/src" 6 | 7 | COPY requirements.txt . 8 | RUN pip install -r requirements.txt 9 | 10 | COPY src/ src/ 11 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | interview: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | volumes: 8 | - ./:/src 9 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==7.1.2 2 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch01_arrays_and_strings/check_permutation.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def check_permutation_sets(string: str, potential_permutation_string: str) -> bool: 5 | return len(string) == len(potential_permutation_string) and set(string) == set(potential_permutation_string) 6 | 7 | 8 | def check_permutation_sort(string: str, potential_permutation_string: str) -> bool: 9 | return sorted(string) == sorted(potential_permutation_string) 10 | 11 | 12 | def check_permutation_array(string: str, potential_permutation_string: str) -> bool: 13 | if len(string) != len(potential_permutation_string): 14 | return False 15 | 16 | url_array = [0] * 128 17 | 18 | for ch in string: 19 | url_array[ord(ch)] += 1 20 | 21 | for ch in potential_permutation_string: 22 | url_array[ord(ch)] -= 1 23 | 24 | if url_array[ord(ch)] < 0: 25 | return False 26 | 27 | return True 28 | 29 | 30 | @pytest.mark.parametrize("string, potential_permutation_string, is_permutation", [ 31 | # @formatter:off 32 | ("god", "dog", True), 33 | ("god", "dod", False), 34 | ("god", "dogg", False), 35 | ("cat belongs to ala", "ala belongs to cat", True), 36 | ("interview questions", "interviews question", True), 37 | ("interview questions", "interview question", False), 38 | # @formatter:on 39 | ]) 40 | @pytest.mark.parametrize("function", [ 41 | check_permutation_sets, 42 | check_permutation_sort, 43 | check_permutation_array, 44 | ]) 45 | def test_algorithm(function, string, potential_permutation_string, is_permutation): 46 | assert function(string, potential_permutation_string) == is_permutation 47 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch01_arrays_and_strings/is_unique.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def check_if_has_unique_characters_pythonic(string: str) -> bool: 5 | return len(set(string)) == len(string) 6 | 7 | 8 | def check_if_has_unique_characters_ascii(string: str) -> bool: 9 | boolean_array = [False] * 128 10 | for ch in string: 11 | int_ch = ord(ch) 12 | if boolean_array[int_ch]: 13 | return False 14 | boolean_array[int_ch] = True 15 | return True 16 | 17 | 18 | def check_if_has_unique_characters_no_structures(string: str) -> bool: 19 | for i, ch_0 in enumerate(string): 20 | for ch_1 in string[i + 1:]: 21 | if ch_0 == ch_1: 22 | return False 23 | return True 24 | 25 | 26 | def check_if_has_unique_characters_no_structures_sort(string: str) -> bool: 27 | sorted_string = sorted(string) 28 | 29 | for i in range(len(sorted_string) - 1): 30 | if sorted_string[i] == sorted_string[i + 1]: 31 | return False 32 | 33 | return True 34 | 35 | 36 | @pytest.mark.parametrize("string, has_all_unique_chars", [ 37 | # @formatter:off 38 | ("qwerty", True), 39 | ("", True), 40 | ("qqwert", False), 41 | ("qwertt", False), 42 | # @formatter:on 43 | ]) 44 | @pytest.mark.parametrize("function", [ 45 | check_if_has_unique_characters_pythonic, 46 | check_if_has_unique_characters_ascii, 47 | check_if_has_unique_characters_no_structures, 48 | check_if_has_unique_characters_no_structures_sort, 49 | ]) 50 | def test_algorithm(function, string, has_all_unique_chars): 51 | assert function(string) == has_all_unique_chars 52 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch01_arrays_and_strings/one_away.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def is_one_edit_away_pythonic(string: str, edit: str) -> bool: 5 | if abs(len(string) - len(edit)) > 1: 6 | return False 7 | 8 | if string in edit or edit in string: 9 | return True 10 | 11 | return len(set(string) - set(edit)) <= 1 12 | 13 | 14 | def is_one_edit_away_loop(string: str, edit: str) -> bool: 15 | if abs(len(string) - len(edit)) > 1: 16 | return False 17 | 18 | shorter_text, longer_text = string if len(string) < len(edit) else edit, string if len(string) >= len(edit) else edit 19 | shorter_i, longer_i = 0, -1 20 | edit_found = False 21 | 22 | while shorter_i < len(shorter_text) and longer_i < len(longer_text): 23 | longer_i += 1 24 | 25 | if shorter_text[shorter_i] == longer_text[longer_i]: 26 | shorter_i += 1 27 | continue 28 | 29 | if edit_found: 30 | return False 31 | 32 | if len(string) == len(edit): 33 | shorter_i += 1 34 | 35 | edit_found = True 36 | 37 | return True 38 | 39 | 40 | @pytest.mark.parametrize("string, edit, expected_result", [ 41 | # @formatter:off 42 | ("pale", "ple", True), 43 | ("pale", "ale", True), 44 | ("ale", "pale", True), 45 | ("pales", "pale", True), 46 | ("pale", "bale", True), 47 | ("pale", "bake", False), 48 | ("pale", "ba", False), 49 | # @formatter:on 50 | ]) 51 | @pytest.mark.parametrize("function", [ 52 | is_one_edit_away_pythonic, 53 | is_one_edit_away_loop 54 | ]) 55 | def test_algorithm(function, string, edit, expected_result): 56 | assert function(string, edit) == expected_result 57 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch01_arrays_and_strings/palindrome_permutation.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | import pytest 4 | 5 | 6 | def is_palindrome_permutation_pythonic(string: str) -> bool: 7 | raw_string = string.replace(' ', '') 8 | letter_frequency = Counter(raw_string) 9 | 10 | if len(raw_string) % 2 == 0: 11 | return all(frequency % 2 == 0 for frequency in letter_frequency.values()) 12 | else: 13 | return sum(1 for frequency in letter_frequency.values() if frequency == 1) <= 1 14 | 15 | 16 | def is_palindrome_permutation_counter(string: str) -> bool: 17 | raw_string = string.replace(' ', '') 18 | letter_frequency = Counter() 19 | num_of_odd = 0 20 | 21 | for ch in raw_string: 22 | letter_frequency[ch] += 1 23 | 24 | if letter_frequency[ch] % 2 == 1: 25 | num_of_odd += 1 26 | else: 27 | num_of_odd -= 1 28 | 29 | return num_of_odd <= 1 30 | 31 | 32 | @pytest.mark.parametrize("string, expected_result", [ 33 | # @formatter:off 34 | ("tact coa", True), 35 | ("kamil slimak", True), 36 | ("slimakkamil ", True), 37 | ("aaaaaab", True), 38 | ("aaa", True), 39 | ("aaaaacb", False), 40 | ("abc", False), 41 | ("slimakoamil ", False), 42 | # @formatter:on 43 | ]) 44 | @pytest.mark.parametrize("function", [ 45 | is_palindrome_permutation_pythonic, 46 | is_palindrome_permutation_counter 47 | ]) 48 | def test_algorithm(function, string, expected_result): 49 | assert function(string) == expected_result 50 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch01_arrays_and_strings/rotate_matrix.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import pytest 4 | 5 | 6 | def rotate_matrix_list_comprehension(matrix: List[List[int]]) -> List[List[int]]: 7 | size = len(matrix) 8 | return [ 9 | [matrix[col][row] for col in reversed(range(size))] 10 | for row in range(size) 11 | ] 12 | 13 | 14 | def rotate_matrix_zip(matrix: List[List[int]]) -> List[List[int]]: 15 | return [list(reversed(row)) for row in zip(*matrix)] 16 | 17 | 18 | @pytest.mark.parametrize("matrix, rotated_matrix", [ 19 | ([[1, 2], 20 | [3, 4]], 21 | [[3, 1], 22 | [4, 2]]), 23 | ([[1, 2, 3], 24 | [4, 5, 6], 25 | [7, 8, 9]], 26 | [[7, 4, 1], 27 | [8, 5, 2], 28 | [9, 6, 3]]), 29 | ([[1, 2, 3, 8], 30 | [4, 5, 6, 8], 31 | [7, 8, 9, 8], 32 | [8, 8, 8, 8]], 33 | [[8, 7, 4, 1], 34 | [8, 8, 5, 2], 35 | [8, 9, 6, 3], 36 | [8, 8, 8, 8]]) 37 | ]) 38 | @pytest.mark.parametrize("function", [ 39 | rotate_matrix_zip, 40 | rotate_matrix_list_comprehension 41 | ]) 42 | def test_algorithm(function, matrix, rotated_matrix): 43 | assert function(matrix) == rotated_matrix 44 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch01_arrays_and_strings/string_compression.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | import pytest 4 | 5 | 6 | def compress_string(text: str) -> str: 7 | @dataclass 8 | class Compressed: 9 | char: str 10 | freq: int 11 | 12 | compressed = [] 13 | 14 | for ch in text: 15 | if compressed and ch == compressed[-1].char: 16 | compressed[-1].freq += 1 17 | else: 18 | compressed.append(Compressed(char=ch, freq=1)) 19 | 20 | return ''.join(f"{c.char}{c.freq}" for c in compressed) if len(compressed) * 2 < len(text) else text 21 | 22 | 23 | @pytest.mark.parametrize("text, expected_result", [ 24 | # @formatter:off 25 | ("a", "a"), 26 | ("aabb", "aabb"), 27 | ("aaaa", "a4"), 28 | ("aabbb", "a2b3"), 29 | ("aabbbaa", "a2b3a2"), 30 | # @formatter:on 31 | ]) 32 | def test_algorithm(text, expected_result): 33 | assert compress_string(text) == expected_result 34 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch01_arrays_and_strings/string_rotation.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def is_rotated(string: str, rotated_string: str) -> bool: 5 | return len(string) == len(rotated_string) and rotated_string in string * 2 6 | 7 | 8 | @pytest.mark.parametrize("string, rotated_string, expected_result", [ 9 | # @formatter:off 10 | ("", "", True), 11 | ("waterbottle", "erbottlewat", True), 12 | ("dog", "gdo", True), 13 | ("dog", "dogdo", False), 14 | ("dog", "godd", False), 15 | ("dog", "go", False), 16 | # @formatter:on 17 | ]) 18 | def test_algorithm(string, rotated_string, expected_result): 19 | assert is_rotated(string, rotated_string) == expected_result 20 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch01_arrays_and_strings/urlify.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def urlify_pythonic(url: str) -> str: 5 | return ' '.join(url.split()).replace(' ', "%20") 6 | 7 | 8 | def urlify_array(url: str) -> str: 9 | result_url = "" 10 | last_appended_character = None 11 | 12 | for ch in url: 13 | if ch == ' ' and last_appended_character is None: 14 | # Do not duplicate '%20' in the URL 15 | continue 16 | elif ch == ' ' and last_appended_character: 17 | last_appended_character = None 18 | result_url += "%20" 19 | else: 20 | last_appended_character = ch 21 | result_url += ch 22 | 23 | if last_appended_character is None: 24 | return result_url[:-3] 25 | 26 | return result_url 27 | 28 | 29 | @pytest.mark.parametrize("url, expected_url", [ 30 | # @formatter:off 31 | ("Mr John Smith", "Mr%20John%20Smith"), 32 | ("Mr John Smith", "Mr%20John%20Smith"), 33 | (" Mr John Smith", "Mr%20John%20Smith"), 34 | ("Mr John Smith ", "Mr%20John%20Smith"), 35 | ("Mr ", "Mr"), 36 | ("M ", "M"), 37 | (" ", ""), 38 | ("", ""), 39 | # @formatter:on 40 | ]) 41 | @pytest.mark.parametrize("function", [ 42 | urlify_pythonic, 43 | urlify_array, 44 | ]) 45 | def test_algorithm(function, url, expected_url): 46 | assert function(url) == expected_url 47 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch01_arrays_and_strings/zero_matrix.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import pytest 4 | 5 | 6 | def nullify_loop(matrix: List[List[int]]) -> List[List[int]]: 7 | height, width = len(matrix), len(matrix[0]) 8 | columns, rows = set(), set() 9 | 10 | for row in range(height): 11 | for col in range(width): 12 | if matrix[row][col] == 0: 13 | columns.add(col) 14 | rows.add(row) 15 | 16 | return [ 17 | [ 18 | 0 if row in rows or col in columns else matrix[row][col] 19 | for col in range(width) 20 | ] 21 | for row in range(height) 22 | ] 23 | 24 | 25 | def nullify_in_place(matrix: List[List[int]]) -> List[List[int]]: 26 | height, width = len(matrix), len(matrix[0]) 27 | 28 | def nullify_column(pos: int) -> None: 29 | for i in range(height): 30 | matrix[i][pos] = 0 31 | 32 | def nullify_row(pos: int) -> None: 33 | matrix[pos] = [0] * width 34 | 35 | col_start = 0 36 | 37 | for row in range(height): 38 | for col in range(col_start, width): 39 | if matrix[row][col] == 0: 40 | nullify_row(row) 41 | nullify_column(col) 42 | 43 | col_start = col + 1 44 | break 45 | 46 | return matrix 47 | 48 | 49 | @pytest.mark.parametrize("matrix, rotated_matrix", [ 50 | ([[0, 2], 51 | [3, 4]], 52 | [[0, 0], 53 | [0, 4]]), 54 | ([[1, 2, 3, 4], 55 | [1, 0, 3, 4], 56 | [1, 2, 3, 0]], 57 | [[1, 0, 3, 0], 58 | [0, 0, 0, 0], 59 | [0, 0, 0, 0]]) 60 | ]) 61 | @pytest.mark.parametrize("function", [ 62 | nullify_loop, 63 | nullify_in_place, 64 | ]) 65 | def test_algorithm(function, matrix, rotated_matrix): 66 | assert function(matrix) == rotated_matrix 67 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch02_linked_lists/delete_middle_node.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from linked_list import ( 4 | LinkedList, 5 | Node, 6 | ) 7 | 8 | 9 | def delete_middle_node(node: Node) -> None: 10 | assert node.next, "node is not the last node in the linked list" 11 | 12 | node.data = node.next.data 13 | node.next = node.next.next 14 | 15 | 16 | @pytest.mark.parametrize("values, node, expected_result", [ 17 | # @formatter:off 18 | ([1, 2, 3, 4], 2, [1, 3, 4]), 19 | ([1, 2, 3, 4], 3, [1, 2, 4]), 20 | # @formatter:on 21 | ]) 22 | def test_algorithm(values, node, expected_result): 23 | linked_list = LinkedList(values) 24 | delete_middle_node(linked_list.node_for_value(node)) 25 | assert linked_list.values == expected_result 26 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch02_linked_lists/intersection.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import pytest 4 | 5 | from linked_list import ( 6 | LinkedList, 7 | Node, 8 | ) 9 | 10 | 11 | def intersection(list_0: LinkedList, list_1: LinkedList) -> Optional[Node]: 12 | if list_0.tail != list_1.tail: 13 | return None 14 | 15 | l0_node, l1_node = list_0.head, list_1.head 16 | l0_len, l1_len = list_0.length, list_1.length 17 | 18 | # Advance pointers when lists have different size: 19 | if l0_len > l1_len: 20 | for i in range(l0_len - l1_len): 21 | l0_node = l0_node.next 22 | 23 | if l0_len < l1_len: 24 | for i in range(l1_len - l0_len): 25 | l1_len = l1_len.next 26 | 27 | while l0_node and l1_node: 28 | if l0_node == l1_node: 29 | return l0_node 30 | l0_node = l0_node.next 31 | l1_node = l1_node.next 32 | 33 | assert False, "Loop above must finish the program" 34 | 35 | 36 | l0 = LinkedList([3, 1, 5, 9]) 37 | l1 = LinkedList([4, 6]) 38 | tail = LinkedList([7, 2, 1]).head 39 | 40 | l4 = LinkedList([3, 1, 5, 9, 7, 2, 1]) 41 | l5 = LinkedList([4, 6, 7, 2, 1]) 42 | 43 | 44 | @pytest.mark.parametrize("list_0, list_0_tail, list_1, list_1_tail, expected_result", [ 45 | # @formatter:off 46 | (l0, tail, l1, tail, tail), 47 | (l4, None, l5, None, None) 48 | # @formatter:on 49 | ]) 50 | def test_algorithm(list_0, list_0_tail, list_1, list_1_tail, expected_result): 51 | list_0.tail.next = list_0_tail 52 | list_1.tail.next = list_1_tail 53 | 54 | assert intersection(list_0, list_1) == expected_result 55 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch02_linked_lists/linked_list.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | List, 3 | Optional, 4 | ) 5 | 6 | import pytest 7 | 8 | 9 | class Node: 10 | def __init__(self, data: int) -> None: 11 | self.next = None 12 | self.data = data 13 | 14 | 15 | class LinkedList: 16 | def __init__(self, data: List[int]) -> None: 17 | self.head = None 18 | for val in data: 19 | self.append(val) 20 | 21 | @property 22 | def values(self) -> List[int]: 23 | result, current = [], self.head 24 | while current: 25 | result.append(current.data) 26 | current = current.next 27 | return result 28 | 29 | @property 30 | def tail(self) -> Optional[Node]: 31 | node = self.head 32 | while node and node.next: 33 | node = node.next 34 | return node 35 | 36 | @property 37 | def length(self) -> int: 38 | return len(self.values) 39 | 40 | def node_for_value(self, val: int) -> Optional[Node]: 41 | node = self.head 42 | while node: 43 | if node.data == val: 44 | return node 45 | node = node.next 46 | return None 47 | 48 | def append(self, data: int) -> None: 49 | self.head = append(self.head, data) 50 | 51 | def delete(self, data: int) -> None: 52 | self.head = delete(self.head, data) 53 | 54 | 55 | def delete(head: Optional[Node], data: int) -> Optional[Node]: 56 | node = head 57 | 58 | if not node: 59 | return None 60 | 61 | if head.data == data: 62 | return head.next 63 | 64 | while node.next: 65 | if node.next.data == data: 66 | node.next = node.next.next 67 | break 68 | node = node.next 69 | 70 | return head 71 | 72 | 73 | def append(head: Optional[Node], data: int) -> Optional[Node]: 74 | if not head: 75 | return Node(data) 76 | 77 | current, end = head, Node(data) 78 | while current.next: 79 | current = current.next 80 | current.next = end 81 | 82 | return head 83 | 84 | 85 | @pytest.mark.parametrize("values", [ 86 | [], 87 | [1], 88 | [1, 2], 89 | [1, 2, 3], 90 | ]) 91 | def test_append(values): 92 | assert LinkedList(values).values == values 93 | 94 | 95 | @pytest.mark.parametrize("values, to_delete, expected_result", [ 96 | # @formatter:off 97 | ([], 0, []), 98 | ([1], 0, [1]), 99 | ([1], 1, []), 100 | ([1, 2], 1, [2]), 101 | ([1, 2], 2, [1]), 102 | ([1, 2, 3], 2, [1, 3]), 103 | # @formatter:on 104 | ]) 105 | def test_delete(values, to_delete, expected_result): 106 | linked_list = LinkedList(values) 107 | linked_list.delete(to_delete) 108 | assert linked_list.values == expected_result 109 | 110 | 111 | @pytest.mark.parametrize("values, value, expected_node_val", [ 112 | # @formatter:off 113 | ([1, 2, 3, 4], 2, 2), 114 | ([1, 2, 3, 4], 5, None) 115 | # @formatter:on 116 | ]) 117 | def test_node_for_value(values, value, expected_node_val): 118 | node = LinkedList(values).node_for_value(value) 119 | assert node.data if node else node == expected_node_val 120 | 121 | 122 | @pytest.mark.parametrize("values, expected_tail", [ 123 | # @formatter:off 124 | ([], None), 125 | ([1], 1), 126 | ([1, 2], 2), 127 | # @formatter:on 128 | ]) 129 | def test_tail(values, expected_tail): 130 | tail = LinkedList(values).tail 131 | assert tail.data == expected_tail if expected_tail else tail is None 132 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch02_linked_lists/loop_detection.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import pytest 4 | 5 | from linked_list import ( 6 | LinkedList, 7 | Node, 8 | ) 9 | 10 | 11 | def get_loop(linked_list: LinkedList) -> Optional[Node]: 12 | slow, fast = linked_list.head, linked_list.head 13 | 14 | def get_loop_head(): 15 | nonlocal slow, fast 16 | slow = linked_list.head 17 | 18 | while slow != fast: 19 | slow = slow.next 20 | fast = fast.next 21 | 22 | return fast 23 | 24 | while fast and fast.next: 25 | slow = slow.next 26 | fast = fast.next.next 27 | 28 | if slow == fast: 29 | return get_loop_head() 30 | 31 | return None 32 | 33 | 34 | l0 = LinkedList([1, 2, 3, 4, 5]) 35 | l0.node_for_value(5).next = l0.node_for_value(3) 36 | 37 | l1 = LinkedList([1, 2, 3, 4, 5]) 38 | 39 | 40 | @pytest.mark.parametrize("linked_list, expected_result", [ 41 | # @formatter:off 42 | (l0, l0.node_for_value(3)), 43 | (l1, None), 44 | # @formatter:on 45 | ]) 46 | def test_algorithm(linked_list, expected_result): 47 | assert get_loop(linked_list) == expected_result 48 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch02_linked_lists/palindrome.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from linked_list import ( 4 | LinkedList, 5 | Node, 6 | ) 7 | 8 | 9 | def is_palindrome_simple(linked_list: LinkedList) -> bool: 10 | values = linked_list.values 11 | return values == values[::-1] 12 | 13 | 14 | def is_palindrome_reverse(linked_list: LinkedList) -> bool: 15 | def reverse_list() -> Node: 16 | head, node = None, linked_list.head 17 | 18 | while node: 19 | new_node = Node(data=node.data) 20 | new_node.next = head 21 | head = new_node 22 | 23 | node = node.next 24 | 25 | return head 26 | 27 | normal_node = linked_list.head 28 | reversed_node = reverse_list() 29 | 30 | while normal_node and reversed_node: 31 | if normal_node.data != reversed_node.data: 32 | return False 33 | normal_node = normal_node.next 34 | reversed_node = reversed_node.next 35 | 36 | return not normal_node and not reversed_node 37 | 38 | 39 | def is_palindrome_slow_fast_runner(linked_list: LinkedList) -> bool: 40 | slow, fast = linked_list.head, linked_list.head 41 | stack = [] 42 | 43 | while fast and fast.next: 44 | stack.append(slow.data) 45 | slow = slow.next 46 | fast = fast.next.next 47 | 48 | if fast: 49 | slow = slow.next 50 | 51 | while slow: 52 | if stack and stack.pop() != slow.data: 53 | return False 54 | slow = slow.next 55 | 56 | return True 57 | 58 | 59 | @pytest.mark.parametrize("values, expected_result", [ 60 | # @formatter:off 61 | ([1, 2, 3, 4], False), 62 | ([1, 2, 2, 2], False), 63 | ([1, 2, 2, 1], True), 64 | ([1, 2, 1], True), 65 | ([1], True), 66 | ([], True) 67 | # @formatter:on 68 | ]) 69 | @pytest.mark.parametrize("function", [ 70 | is_palindrome_simple, 71 | is_palindrome_reverse, 72 | is_palindrome_slow_fast_runner, 73 | ]) 74 | def test_algorithm(function, values, expected_result): 75 | linked_list = LinkedList(values) 76 | assert function(linked_list) == expected_result 77 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch02_linked_lists/partition.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | import pytest 4 | from linked_list import LinkedList 5 | 6 | 7 | def partition(linked_list: LinkedList, partition_val: int) -> Tuple[LinkedList, LinkedList]: 8 | l1, l2 = LinkedList(data=[]), LinkedList(data=[]) 9 | node = linked_list.head 10 | 11 | while node: 12 | if node.data < partition_val: 13 | l1.append(node.data) 14 | else: 15 | l2.append(node.data) 16 | node = node.next 17 | 18 | return l1, l2 19 | 20 | 21 | @pytest.mark.parametrize("values, partition_val, expected_values", [ 22 | # @formatter:off 23 | ([1, 2, 3, 4, 5], 3, ([1, 2], [3, 4, 5])), 24 | ([1, 2, 3, 4, 5], 0, ([], [1, 2, 3, 4, 5])), 25 | ([1, 2, 3, 4, 5], 6, ([1, 2, 3, 4, 5], [])), 26 | # @formatter:on 27 | ]) 28 | def test_algorithm(values, partition_val, expected_values): 29 | linked_list = LinkedList(values) 30 | l1, l2 = partition(linked_list, partition_val) 31 | assert (l1.values, l2.values) == expected_values 32 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch02_linked_lists/remove_dups.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from linked_list import LinkedList 4 | 5 | 6 | def remove_duplicates_buffer(linked_list: LinkedList) -> LinkedList: 7 | unique_data = set() 8 | prev, current = None, linked_list.head 9 | 10 | while current: 11 | if current.data in unique_data: 12 | prev.next = current.next 13 | else: 14 | unique_data.add(current.data) 15 | prev = current 16 | current = current.next 17 | 18 | return linked_list 19 | 20 | 21 | def remove_duplicates_no_buffer(linked_list: LinkedList) -> LinkedList: 22 | current = linked_list.head 23 | 24 | while current: 25 | runner = current 26 | 27 | while runner.next: 28 | if current.data == runner.next.data: 29 | runner.next = runner.next.next 30 | else: 31 | runner = runner.next 32 | 33 | current = current.next 34 | 35 | return linked_list 36 | 37 | 38 | @pytest.mark.parametrize("values, expected_result", [ 39 | # @formatter:off 40 | ([], []), 41 | ([1, 1], [1]), 42 | ([1, 1, 0], [1, 0]), 43 | ([1, 1, 1, 1], [1]), 44 | ([0, 1, 0, 1], [0, 1]), 45 | ([1, 2, 3, 4], [1, 2, 3, 4]), 46 | # @formatter:on 47 | ]) 48 | @pytest.mark.parametrize("function", [ 49 | remove_duplicates_buffer, 50 | remove_duplicates_no_buffer 51 | ]) 52 | def test_algorithm(function, values, expected_result): 53 | linked_list = LinkedList(values) 54 | assert function(linked_list).values == expected_result 55 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch02_linked_lists/return_kth_to_last.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import pytest 4 | 5 | from linked_list import ( 6 | LinkedList, 7 | Node, 8 | ) 9 | 10 | 11 | def return_kth_to_last_simple(linked_list: LinkedList, k: int) -> int: 12 | node = linked_list.head 13 | position, i = len(linked_list.values) - k, 0 14 | 15 | if position < 0: 16 | return -1 17 | 18 | while node and i < position: 19 | node = node.next 20 | i += 1 21 | 22 | return node.data 23 | 24 | 25 | def return_kth_to_last_simplest(linked_list: LinkedList, k: int) -> int: 26 | values = linked_list.values 27 | size = len(values) 28 | 29 | return values[size - k] if size - k >= 0 else -1 30 | 31 | 32 | def return_kth_to_last_recursive(linked_list: LinkedList, k: int) -> int: 33 | found_value = None 34 | 35 | def _return_kth_to_last(node: Optional[Node]) -> int: 36 | if not node: 37 | return 0 38 | 39 | index = _return_kth_to_last(node.next) + 1 40 | 41 | if index == k: 42 | nonlocal found_value 43 | found_value = node.data 44 | 45 | return index 46 | 47 | _return_kth_to_last(linked_list.head) 48 | 49 | return found_value if found_value else -1 50 | 51 | 52 | def return_kth_to_last_iterative(linked_list: LinkedList, k: int) -> int: 53 | p1, p2 = linked_list.head, linked_list.head 54 | 55 | for _ in range(k): 56 | if not p1: 57 | return -1 58 | p1 = p1.next 59 | 60 | while p1: 61 | p1 = p1.next 62 | p2 = p2.next 63 | 64 | return p2.data 65 | 66 | 67 | @pytest.mark.parametrize("values, k, expected_result", [ 68 | # @formatter:off 69 | ([1, 2, 3], 1, 3), 70 | ([1, 2, 3], 2, 2), 71 | ([1, 2, 3], 3, 1), 72 | ([1, 2, 3], 4, -1), 73 | # @formatter:on 74 | ]) 75 | @pytest.mark.parametrize("function", [ 76 | return_kth_to_last_simple, 77 | return_kth_to_last_simplest, 78 | return_kth_to_last_recursive, 79 | return_kth_to_last_iterative, 80 | ]) 81 | def test_algorithm(function, values, k, expected_result): 82 | linked_list = LinkedList(values) 83 | assert function(linked_list, k) == expected_result 84 | -------------------------------------------------------------------------------- /books/cracking-coding-interview/src/ch02_linked_lists/sum_lists.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from linked_list import ( 4 | LinkedList, 5 | Node, 6 | ) 7 | 8 | 9 | def sum_lists(list_0: LinkedList, list_1: LinkedList) -> LinkedList: 10 | result, remainder = [], 0 11 | node_0, node_1 = list_0.head, list_1.head 12 | 13 | def add_aligned_lists() -> None: 14 | nonlocal node_0, node_1, result, remainder 15 | while node_0 and node_1: 16 | result.append((node_0.data + node_1.data + remainder) % 10) 17 | remainder = 1 if (node_0.data + node_1.data + remainder) >= 10 else 0 18 | node_0, node_1 = node_0.next, node_1.next 19 | 20 | def align_remaining_list(node: Node) -> None: 21 | nonlocal result, remainder 22 | while node: 23 | result.append((node.data + remainder) % 10) 24 | remainder = 1 if (node.data + remainder) >= 10 else 0 25 | node = node.next 26 | 27 | add_aligned_lists() 28 | align_remaining_list(node_0) 29 | align_remaining_list(node_1) 30 | 31 | if remainder: 32 | result.append(remainder) 33 | 34 | return LinkedList(result) 35 | 36 | 37 | @pytest.mark.parametrize("list_0, list_1, expected_result", [ 38 | # @formatter:off 39 | ([7, 1, 6], [5, 9, 2], [2, 1, 9]), 40 | ([1, 7, 1], [3], [4, 7, 1]), 41 | ([9, 9, 9], [1], [0, 0, 0, 1]), 42 | ([7, 1], [3, 1], [0, 3]), 43 | ([7, 1], [3], [0, 2]), 44 | # @formatter:on 45 | ]) 46 | def test_algorithm(list_0, list_1, expected_result): 47 | list_0, list_1 = LinkedList(list_0), LinkedList(list_1) 48 | assert sum_lists(list_0, list_1).values == expected_result 49 | -------------------------------------------------------------------------------- /books/docker-deep-dive.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Docker Deep Dive 4 | 5 | Book by Nigel Poulton -------------------------------------------------------------------------------- /books/elixir.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Elixir in Action 4 | 5 | Book by Saša Jurić 6 | -------------------------------------------------------------------------------- /books/go/ch01/Makefile: -------------------------------------------------------------------------------- 1 | # Set default target, when 'make' executed, runs 'build' by default: 2 | .DEFAULT_GOAL := build 3 | 4 | fmt: 5 | go fmt ./... 6 | # Keep 'make' from getting confused with directories, in this case with directory 'fmt' (if it is ever created): 7 | .PHONY: fmt 8 | 9 | # Before running 'lint', run 'fmt' 10 | lint: fmt 11 | golint ./... 12 | .PHONY: lint 13 | 14 | vet: fmt 15 | go vet ./... 16 | .PHONY: vet 17 | 18 | build: vet 19 | go build hello.go 20 | .PHONY: build 21 | -------------------------------------------------------------------------------- /books/go/ch01/hello.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("Hello, world!") 7 | } 8 | -------------------------------------------------------------------------------- /books/go/ch02/const.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | const x int64 = 10 6 | 7 | const ( 8 | idKey = "id" 9 | nameKey = "name" 10 | ) 11 | 12 | const z = 20 * 20 13 | 14 | func main() { 15 | const y = "hello" 16 | 17 | fmt.Println(x) 18 | fmt.Println(y) 19 | 20 | //x = x + 1 // Error 21 | //y = "bye" // Error 22 | 23 | fmt.Println(x) 24 | fmt.Println(y) 25 | } 26 | -------------------------------------------------------------------------------- /books/go/ch02/unicode.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ęąćśż := "hello" 7 | fmt.Println(ęąćśż) 8 | } 9 | -------------------------------------------------------------------------------- /books/go/ch03/types.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var x [3]int 7 | fmt.Println(x) 8 | 9 | var y = [12]int{1, 5: 4} 10 | fmt.Println(y) 11 | 12 | var z = [...]int{12, 20, 30} 13 | fmt.Println(z) 14 | 15 | var p = []int{12, 20, 30} 16 | fmt.Println(p) 17 | 18 | var v []int 19 | fmt.Println(v == nil) 20 | fmt.Println(len(v)) 21 | v = append(v, 10, 20) 22 | fmt.Println(v) 23 | v = append(v, p...) 24 | fmt.Println(v) 25 | fmt.Println(cap(v)) 26 | 27 | r := make([]int, 5) 28 | fmt.Println(r) 29 | r = make([]int, 0, 20) 30 | r = append(r, 10, 20) 31 | fmt.Println(r) 32 | 33 | s := "Hello 😇" 34 | fmt.Println(s[6:7]) 35 | fmt.Println(s[6:10]) // 4 bytes for emoji 36 | 37 | teams := map[string][]string{ 38 | "Orcas": {"Fred", "Ralph"}, 39 | "Lions": {"Sarah", "Peter"}, 40 | } 41 | fmt.Println(teams) 42 | team, ok := teams["Kittens"] 43 | fmt.Println(team, ok) 44 | 45 | set := map[int]bool{} 46 | vals := []int{1, 2, 3, 4, 5, 6, 7, 4, 3, 2, 3, 4, 3} 47 | for _, v := range vals { 48 | set[v] = true 49 | } 50 | fmt.Println(len(set), len(vals)) 51 | if set[1] { 52 | fmt.Println("1 is in the set") 53 | } 54 | 55 | type person struct { 56 | name string 57 | age int 58 | pet string 59 | } 60 | julia := person{ 61 | "Julia", 62 | 30, 63 | "cat", 64 | } 65 | beth := person{ 66 | name: "Beth", 67 | } 68 | fmt.Println(julia, beth) 69 | 70 | var bob struct { 71 | name string 72 | age int 73 | pet string 74 | } 75 | bob.name = "Bob" 76 | fmt.Println(bob) 77 | } 78 | -------------------------------------------------------------------------------- /books/go/ch04/case.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | words := []string{"a", "cow", "smile", "gopher"} 7 | 8 | for _, word := range words { 9 | switch size := len(word); size { 10 | case 1, 2, 3, 4: 11 | fmt.Println(word, "is a short word!") 12 | case 5: 13 | wordLen := len(word) 14 | fmt.Println(word, "is the exactly the right length:", wordLen) 15 | case 6, 7, 8, 9: 16 | default: 17 | fmt.Println(word, "is a long word!") 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /books/go/ch04/for.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | completeFor() 9 | conditionOnlyFor() 10 | infiniteFor() 11 | forRange() 12 | labelingStatements() 13 | } 14 | 15 | func completeFor() { 16 | for i := 0; i < 10; i++ { 17 | fmt.Println(i) 18 | } 19 | } 20 | 21 | func conditionOnlyFor() { 22 | i := 1 23 | for i < 100 { 24 | fmt.Println(i) 25 | i = i * 2 26 | } 27 | } 28 | 29 | func infiniteFor() { 30 | for { 31 | fmt.Println("Hello") 32 | break 33 | } 34 | } 35 | 36 | func forRange() { 37 | evenVals := []int{2, 4, 6, 8, 10, 12} 38 | for i, v := range evenVals { 39 | fmt.Println(i, v) 40 | } 41 | 42 | for _, v := range evenVals { 43 | fmt.Println(v) 44 | } 45 | 46 | for _, v := range evenVals { 47 | fmt.Println(v) 48 | } 49 | 50 | uniqueNames := map[string]bool{"Fred": true, "Paul": true, "Wilma": true} 51 | for k := range uniqueNames { 52 | fmt.Println(k) 53 | } 54 | } 55 | 56 | func labelingStatements() { 57 | samples := []string{"hello", "apple_π!"} 58 | outer: 59 | for _, sample := range samples { 60 | for i, r := range sample { 61 | fmt.Println(i, r, string(r)) 62 | if r == 'l' { 63 | continue outer 64 | } 65 | } 66 | fmt.Println() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /books/go/ch04/if.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | ) 7 | 8 | func main() { 9 | if n := rand.Intn(10); n == 10 { 10 | fmt.Println("That's too low") 11 | } else if n > 5 { 12 | fmt.Println("That's too big:", n) 13 | } else { 14 | fmt.Println("That's a good number:", n) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /books/go/ch05/anonymous.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | for i := 0; i < 5; i++ { 7 | func(j int) { 8 | fmt.Println("printing", j, "from inside of an anonymous function") 9 | }(i) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /books/go/ch05/deferExample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "os" 7 | ) 8 | 9 | func getFile(name string) (*os.File, func(), error) { 10 | f, err := os.Open(name) 11 | if err != nil { 12 | return nil, nil, err 13 | } 14 | return f, func() { 15 | f.Close() 16 | }, nil 17 | } 18 | 19 | func main() { 20 | if len(os.Args) < 2 { 21 | log.Fatal("no file specified") 22 | } 23 | f, closer, err := getFile(os.Args[1]) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | defer closer() 28 | data := make([]byte, 2048) 29 | for { 30 | count, err := f.Read(data) 31 | os.Stdout.Write(data[:count]) 32 | if err != nil { 33 | if err != io.EOF { 34 | log.Fatal(err) 35 | } 36 | break 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /books/go/ch05/functionAsParam.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | ) 7 | 8 | type Person struct { 9 | FirstName string 10 | LastName string 11 | Age int 12 | } 13 | 14 | func main() { 15 | people := []Person{ 16 | {"Pat", "Patterson", 34}, 17 | {"Tracy", "Bobbert", 23}, 18 | {"Fred", "Fredson", 18}, 19 | } 20 | sort.Slice(people, func(i int, j int) bool { 21 | return people[i].LastName < people[j].LastName 22 | }) 23 | fmt.Println(people) 24 | } 25 | -------------------------------------------------------------------------------- /books/go/ch05/functions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | result := Div(5, 2) 10 | fmt.Println(result) 11 | 12 | MyFunc(MyFuncOpts{ 13 | LastName: "Smith", 14 | Age: 10, 15 | }) 16 | 17 | fmt.Println(addTo(10, 1, 2, 3, 4, 5)) 18 | fmt.Println(addTo(10, []int{1, 2, 3, 4, 5}...)) 19 | 20 | result, remainder, err := divAndRemainder(5, 2) 21 | if err != nil { 22 | fmt.Println(err) 23 | } 24 | fmt.Println(result, remainder) 25 | } 26 | 27 | func Div(numerator int, denominator int) int { 28 | if denominator == 0 { 29 | return 0 30 | } 31 | return numerator / denominator 32 | } 33 | 34 | type MyFuncOpts struct { 35 | FirstName string 36 | LastName string 37 | Age int 38 | } 39 | 40 | func MyFunc(opts MyFuncOpts) int { 41 | return opts.Age 42 | } 43 | 44 | func addTo(base int, vals ...int) []int { 45 | out := make([]int, 0, len(vals)) 46 | for _, v := range vals { 47 | out = append(out, base+v) 48 | } 49 | return out 50 | } 51 | 52 | func divAndRemainder(numerator int, denominator int) (result int, remainder int, err error) { 53 | if denominator == 0 { 54 | err = errors.New("cannot divide by zero") 55 | return result, remainder, err 56 | } 57 | result, remainder, err = numerator/denominator, numerator%denominator, nil 58 | return result, remainder, err 59 | } 60 | -------------------------------------------------------------------------------- /books/go/ch05/functionsAreValues.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var opMap = map[string]func(int, int) int{ 7 | "+": add, 8 | "-": sub, 9 | "*": mul, 10 | "/": div, 11 | } 12 | 13 | fmt.Println(opMap["+"](10, 20)) 14 | } 15 | 16 | func add(i int, j int) int { return i + j } 17 | func sub(i int, j int) int { return i - j } 18 | func mul(i int, j int) int { return i * j } 19 | func div(i int, j int) int { return i / j } 20 | -------------------------------------------------------------------------------- /books/go/ch05/returnFunction.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func makeMult(base int) func(int) int { 6 | return func(factor int) int { 7 | return base * factor 8 | } 9 | } 10 | 11 | func main() { 12 | twoBase := makeMult(2) 13 | threeBase := makeMult(3) 14 | 15 | for i := 0; i < 3; i++ { 16 | fmt.Println(twoBase(i), threeBase(i)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /books/go/ch06/pointers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func failedUpdate(px *int) { 6 | x2 := 20 7 | px = &x2 8 | } 9 | 10 | func update(px *int) { 11 | *px = 20 12 | } 13 | 14 | func main() { 15 | y := "hello" 16 | fmt.Println(y, &y, *&y) 17 | 18 | x := 10 19 | failedUpdate(&x) 20 | fmt.Println(x) 21 | update(&x) 22 | fmt.Println(x) 23 | } 24 | -------------------------------------------------------------------------------- /books/go/ch07/counter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type Counter struct { 9 | total int 10 | lastUpdated time.Time 11 | } 12 | 13 | func (c *Counter) Increment() { 14 | c.total++ 15 | c.lastUpdated = time.Now() 16 | } 17 | 18 | func (c Counter) String() string { 19 | return fmt.Sprintf("total: %d, last updated %v", c.total, c.lastUpdated) 20 | } 21 | 22 | func updateWrong(c Counter) { 23 | c.Increment() 24 | fmt.Println("in updateWrong:", c.String()) 25 | } 26 | 27 | func updateRight(c *Counter) { 28 | c.Increment() 29 | fmt.Println("in updateRight:", c.String()) 30 | } 31 | 32 | func main() { 33 | var c Counter 34 | fmt.Println(c.String()) 35 | c.Increment() 36 | fmt.Println(c.String()) 37 | 38 | updateWrong(c) 39 | fmt.Println("in main:", c.String()) 40 | updateRight(&c) 41 | fmt.Println("in main:", c.String()) 42 | } 43 | -------------------------------------------------------------------------------- /books/go/ch07/dependencyInjection.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | func LogOutput(message string) { 10 | fmt.Println(message) 11 | } 12 | 13 | type SimpleDataStore struct { 14 | userData map[string]string 15 | } 16 | 17 | func (sds SimpleDataStore) UserNameForId(userID string) (string, bool) { 18 | name, ok := sds.userData[userID] 19 | return name, ok 20 | } 21 | 22 | func NewSimpleDataStore() SimpleDataStore { 23 | return SimpleDataStore{ 24 | userData: map[string]string{ 25 | "1": "Fred", 26 | "2": "Mary", 27 | "3": "Pat", 28 | }, 29 | } 30 | } 31 | 32 | type DataStore interface { 33 | UserNameForId(userID string) (string, bool) 34 | } 35 | 36 | type Logger interface { 37 | Log(message string) 38 | } 39 | 40 | type LoggerAdapter func(message string) 41 | 42 | func (lg LoggerAdapter) Log(message string) { 43 | lg(message) 44 | } 45 | 46 | type SimpleLogic struct { 47 | l Logger 48 | ds DataStore 49 | } 50 | 51 | func (sl SimpleLogic) SayHello(userID string) (string, error) { 52 | sl.l.Log("in SayHello for " + userID) 53 | name, ok := sl.ds.UserNameForId(userID) 54 | if !ok { 55 | return "", errors.New("unknown user") 56 | } 57 | return "Hello, " + name, nil 58 | } 59 | 60 | func (sl SimpleLogic) SayGoodbye(userID string) (string, error) { 61 | sl.l.Log("in SayGoodbye for " + userID) 62 | name, ok := sl.ds.UserNameForId(userID) 63 | if !ok { 64 | return "", errors.New("unknown user") 65 | } 66 | return "Goodbye, " + name, nil 67 | } 68 | 69 | func NewSimpleLogic(l Logger, ds DataStore) SimpleLogic { 70 | return SimpleLogic{ 71 | l: l, 72 | ds: ds, 73 | } 74 | } 75 | 76 | type MyLogic interface { 77 | SayHello(userID string) (string, error) 78 | } 79 | 80 | type Controller struct { 81 | l Logger 82 | logic MyLogic 83 | } 84 | 85 | func (c Controller) SayHello(w http.ResponseWriter, r *http.Request) { 86 | c.l.Log("In SayHello") 87 | userID := r.URL.Query().Get("user_id") 88 | message, err := c.logic.SayHello(userID) 89 | if err != nil { 90 | w.WriteHeader(http.StatusBadRequest) 91 | w.Write([]byte(err.Error())) 92 | return 93 | } 94 | w.Write([]byte(message)) 95 | } 96 | 97 | func NewController(l Logger, logic MyLogic) Controller { 98 | return Controller{ 99 | l: l, 100 | logic: logic, 101 | } 102 | } 103 | 104 | func main() { 105 | l := LoggerAdapter(LogOutput) 106 | ds := NewSimpleDataStore() 107 | logic := NewSimpleLogic(l, ds) 108 | c := NewController(l, logic) 109 | http.HandleFunc("/hello", c.SayHello) 110 | http.ListenAndServe(":8080", nil) 111 | } 112 | -------------------------------------------------------------------------------- /books/go/ch07/embedding.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Employee struct { 6 | Name string 7 | ID string 8 | } 9 | 10 | func (e Employee) Description() string { 11 | return fmt.Sprintf("%s (%s)", e.Name, e.ID) 12 | } 13 | 14 | type Manager struct { 15 | Employee 16 | Reports []Employee 17 | } 18 | 19 | func main() { 20 | m := Manager{ 21 | Employee: Employee{ 22 | Name: "Bob Bobson", 23 | ID: "12345", 24 | }, 25 | Reports: []Employee{}, 26 | } 27 | 28 | fmt.Println(m.ID) 29 | fmt.Println(m.Description()) 30 | } 31 | -------------------------------------------------------------------------------- /books/go/ch07/intTree.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "log" 4 | 5 | type IntTree struct { 6 | val int 7 | left, right *IntTree 8 | } 9 | 10 | func (it *IntTree) Insert(val int) *IntTree { 11 | if it == nil { 12 | return &IntTree{val: val} 13 | } 14 | 15 | if val < it.val { 16 | it.left = it.left.Insert(val) 17 | } else if val > it.val { 18 | it.right = it.right.Insert(val) 19 | } 20 | 21 | return it 22 | } 23 | 24 | func (it *IntTree) Contains(val int) bool { 25 | switch { 26 | case it == nil: 27 | return false 28 | case val < it.val: 29 | return it.left.Contains(val) 30 | case val > it.val: 31 | return it.right.Contains(val) 32 | default: 33 | return true 34 | } 35 | } 36 | 37 | func main() { 38 | var it *IntTree 39 | it = it.Insert(5) // calling methods on a nil receiver 40 | it = it.Insert(3) 41 | it = it.Insert(10) 42 | it = it.Insert(2) 43 | 44 | log.Println(it.Contains(2)) 45 | log.Println(it.Contains(12)) 46 | } 47 | -------------------------------------------------------------------------------- /books/go/ch07/interfaces.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type LogicProvider struct{} 6 | 7 | func (lp LogicProvider) Process(data string) string { 8 | return data 9 | } 10 | 11 | type Logic interface { 12 | Process(data string) string 13 | } 14 | 15 | type Client struct { 16 | L Logic 17 | } 18 | 19 | func (c Client) Program() { 20 | data := "whatever" 21 | c.L.Process(data) 22 | } 23 | 24 | func main() { 25 | c := Client{L: LogicProvider{}} 26 | c.Program() 27 | 28 | var i interface{} 29 | i = 1 30 | i = "a" 31 | fmt.Println(i) 32 | } 33 | -------------------------------------------------------------------------------- /books/go/ch07/iota.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type MailCategory int 4 | 5 | const ( 6 | Uncategorized MailCategory = iota 7 | Personal 8 | Spam 9 | Social 10 | Ads 11 | ) 12 | -------------------------------------------------------------------------------- /books/go/ch07/types.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Person struct { 6 | FirstName string 7 | LastName string 8 | Age int 9 | } 10 | 11 | type King Person // this is not an inheritance 12 | 13 | func (p Person) String() string { 14 | return fmt.Sprintf("%s %s, age %d", p.FirstName, p.LastName, p.Age) 15 | } 16 | 17 | type Score int 18 | type Converter func(string) Score 19 | type TeamScore map[string]Score 20 | 21 | func main() { 22 | p := Person{ 23 | FirstName: "Fred", 24 | LastName: "Fredson", 25 | Age: 52, 26 | } 27 | fmt.Println(p.String()) 28 | } 29 | -------------------------------------------------------------------------------- /books/go/ch08/customErrors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Status int 4 | 5 | const ( 6 | InvalidLogin Status = iota + 1 7 | NotFound 8 | ) 9 | 10 | type StatusErr struct { 11 | Status Status 12 | Message string 13 | err error 14 | } 15 | 16 | func (se StatusErr) Error() string { 17 | return se.Message 18 | } 19 | 20 | func (se StatusErr) Unwrap() error { 21 | return se.err 22 | } 23 | -------------------------------------------------------------------------------- /books/go/ch08/errors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func calcRemainderAndMod(numerator, denominator int) (int, int, error) { 10 | if denominator == 0 { 11 | return 0, 0, errors.New("denominator is 0") 12 | } 13 | return numerator / denominator, numerator % denominator, nil 14 | } 15 | 16 | func main() { 17 | numerator := 20 18 | denominator := 3 19 | remainder, mod, err := calcRemainderAndMod(numerator, denominator) 20 | if err != nil { 21 | fmt.Println(err) 22 | os.Exit(1) 23 | } 24 | fmt.Println(remainder, mod) 25 | } 26 | -------------------------------------------------------------------------------- /books/go/ch08/panic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func doPanic(msg string) { 4 | panic(msg) 5 | } 6 | 7 | func main() { 8 | doPanic("ERR") 9 | } 10 | -------------------------------------------------------------------------------- /books/go/ch08/recover.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func div60(i int) { 6 | defer func() { 7 | if v := recover(); v != nil { 8 | fmt.Println(v) 9 | } 10 | }() 11 | fmt.Println(60 / i) 12 | } 13 | 14 | func main() { 15 | for _, val := range []int{1, 2, 0, 6} { 16 | div60(val) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /books/go/ch08/sentinel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "fmt" 7 | ) 8 | 9 | type Sentinel string 10 | 11 | func (s Sentinel) Error() string { 12 | return string(s) 13 | } 14 | 15 | const ( 16 | ErrFoo = Sentinel("foo err") 17 | ErrBar = Sentinel("bar err") 18 | ) 19 | 20 | func main() { 21 | data := []byte("This is not a zip file") 22 | notZipFile := bytes.NewReader(data) 23 | _, err := zip.NewReader(notZipFile, int64(len(data))) 24 | if err == zip.ErrFormat { 25 | fmt.Println("Told you so") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /books/go/ch08/wrappingErrors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func fileChecker(name string) error { 10 | f, err := os.Open(name) 11 | if err != nil { 12 | return fmt.Errorf("in fileChecker: %w", err) // %w wraps the error 13 | //return fmt.Errorf("in fileChecker: %v", err) // %v does not wrap the error 14 | } 15 | f.Close() 16 | return nil 17 | } 18 | 19 | func main() { 20 | err := fileChecker("not_here.txt") 21 | if err != nil { 22 | fmt.Println(err) 23 | if wrappedErr := errors.Unwrap(err); wrappedErr != nil { 24 | fmt.Println(wrappedErr) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /books/go/ch09/formatter/formatter.go: -------------------------------------------------------------------------------- 1 | package print 2 | 3 | import "fmt" 4 | 5 | func Format(num int) string { 6 | return fmt.Sprintf("The number is %d", num) 7 | } 8 | -------------------------------------------------------------------------------- /books/go/ch09/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "./formatter" 5 | "./math" 6 | "fmt" 7 | ) 8 | 9 | func main() { 10 | num := math.Double(2) 11 | output := print.Format(num) 12 | fmt.Println(output) 13 | } 14 | -------------------------------------------------------------------------------- /books/go/ch09/math/math.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | func Double(a int) int { 4 | return a * 2 5 | } 6 | -------------------------------------------------------------------------------- /books/go/ch10/deadlock.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ch1 := make(chan int) 7 | ch2 := make(chan int) 8 | 9 | go func() { 10 | v := 1 11 | ch1 <- v 12 | v2 := <-ch2 13 | fmt.Println(v2) 14 | }() 15 | 16 | v := 2 17 | ch2 <- v 18 | v2 := <-ch1 19 | fmt.Println(v, v2) 20 | } 21 | -------------------------------------------------------------------------------- /books/go/ch10/deadlockSolution.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ch1 := make(chan int) 7 | ch2 := make(chan int) 8 | 9 | go func() { 10 | v := 1 11 | ch1 <- v 12 | v2 := <-ch2 13 | fmt.Println(v2) 14 | }() 15 | 16 | v := 2 17 | var v2 int 18 | 19 | select { 20 | case ch2 <- v: 21 | case v2 = <-ch1: 22 | } 23 | 24 | fmt.Println(v, v2) 25 | } 26 | -------------------------------------------------------------------------------- /books/go/ch10/goroutinesExample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func process(val int) int { 4 | return val * 2 5 | } 6 | 7 | func runThingConcurrently(in <-chan int, out chan<- int) { 8 | go func() { 9 | for val := range in { 10 | result := process(val) 11 | out <- result 12 | } 13 | }() 14 | } 15 | -------------------------------------------------------------------------------- /books/hands-on-ml.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow: Concepts, Tools, and Techniques to Build Intelligent Systems 4 | 5 | Book by Aurelien Geron 6 | 7 | [TOC] 8 | 9 | TODO: *Re-read Part I.* 10 | 11 | ## Chapter 10: Introduction to Artificial Neural Networks with Keras 12 | 13 | ANNs - Artificial Neural Networks - inspired by the networks of biological neurons, have gradually become quite 14 | different from their biological cousins. 15 | 16 | ANNs introduced in 1943 by McCulloch and Pitts - a simplified computational model of how biological neurons might work 17 | together in animal brains to perform complex computation using propositional logic. 18 | 19 | McCulloch and Pitts proposed an artificial neuron that has one or more binary inputs (on/off) and one binary output. The 20 | artificial neuron activates its output when more than a certain number of its inputs are active. They showed that even 21 | such simplified model is possible capable of performing various logical computations. 22 | 23 | The Perceptron - is one of the simplest ANN architectures, invented in 1957. It is based on slightly different 24 | artificial neuron - threshold logic unit or linear threshold unit. The inputs and outputs are numbers (instead of binary 25 | on/off values) and each input connection is associated with a weight. The TLU computes a weighted sum of its inputs, 26 | then applies a step function to that sum and outputs the result. Most commonly used step function is the Heaviside step 27 | function. 28 | 29 | A single TLU can be used for simple linear binary classification. 30 | 31 | A perceptron is composed of a single layer of TLUs, each TLU connected to all inputs (when all the neurons are connected 32 | to every neuron in the previous layer, the layer is called a fully connected layer). The inputs of the Perceptron are 33 | fed to special passthrough neurons from the input layer. Extra bias feature is generally added (neuron that always 34 | output 1). A Perceptron with 2 inputs and three outputs can classify instances simultaneously into three different 35 | binary classes - multi-output classifier. 36 | 37 | How perceptron is trained? "Cells that fire together, wire together" - the connection between weight between 2 neurons 38 | tend to increase when they fire simultaneously (Hebb's rule). The perceptron is fed one example at a time, when it 39 | outputs wrong answer, it reinforces the connection weights from the inputs that would have contributed to the correct 40 | answer. 41 | 42 | In fact single Perceptron is similar to SDGClassifier. 43 | 44 | Back-propagation training algorithm - it is Gradient Descent using an efficient technique for computing the gradients 45 | automatically in just 2 passes through network - one forward, one backward. It can find out how each connection weight 46 | and each bias term should be tweaked in order to reduce the error. 47 | 48 | In other words: for each training instance, the back propagation algorithm first makes a prediction (forward pass) and 49 | measures the error, then goes through each layer in reverse to measure the error contribution from each connection ( 50 | reverse pass) and finally tweaks the connection weights to reduce error (Gradient Descent step). 51 | 52 | When building MLP for regression you don't want to use any activation function for the output neurons, so they are free 53 | to output any range of values. If output needs to be always positive ReLU can be used in the output layer. 54 | 55 | The loss function to use during training is typically the mean squared error, but if there are many outliers in 56 | the training set, mean absolute error might be a better choice. 57 | 58 | MLP can be used also for classification. 59 | 60 | Tensorflow 2 adopted Keras' high-level API + introduced some additional functionalities. 61 | 62 | **Sequential API** - the simplest kind of Keras model for neural networks that are just composed of a single stack of layers 63 | connected sequentially. Flatten - preprocessing layer whose role is to convert each input into 1D array. Once model is 64 | defined it needs to be compiled - you need to specify loss function and optimiser to use, optionally list of metrics can 65 | be passed. Then model can be trained. 66 | 67 | If the training set is very skewed, with some classes being overrepresented and others underrepresented, it would be 68 | useful to set the class_weight argument when calling the fit method. 69 | 70 | If you are not satisfied with model's performance - adjust hyperparametrs if longer training is not bringing any 71 | additional benefits. 72 | 73 | Model estimates probabilities per class. 74 | 75 | When layers are created they are called like functions -> `keras.layers.Dense(30)(prev_layer)` - This is why it is 76 | called the **Functional API**, this is the way of telling Keras how to join layers. 77 | 78 | A model can have multiple inputs and multiple outputs, depending on the task. 79 | 80 | Sequential API and Functional API are declarative, for more declarative programming style is **Subclassing API**. Simply 81 | subclass the `Model` class, create layers in the constructor and use them to perform computations in the `call` method. 82 | Subclassing API is very limited, it does not allow viewing model's summary, also Keras na not inspect the model ahead of 83 | time. So Sequential and Functional APIs are preferred. 84 | 85 | It is possible to save and load Keras model to/from disk. Keras will use HDF5 format to save model's architecture and 86 | all the values of all the model parameters for every layer (weights and bias). When training enormous model, it is a 87 | good idea to save checkpoints at regular intervals during training to avoid loosing everything if computer crashes. In 88 | order to make checkpoints you have to use callbacks. 89 | 90 | 91 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_01_strategy.py: -------------------------------------------------------------------------------- 1 | class FlyBehavior: 2 | def fly(self) -> None: 3 | raise NotImplementedError 4 | 5 | 6 | class QuackBehavior: 7 | def quack(self) -> None: 8 | raise NotImplementedError 9 | 10 | 11 | class Duck: 12 | def __init__(self, fly_behavior: FlyBehavior, quack_behavior: QuackBehavior) -> None: 13 | self.fly_behavior = fly_behavior 14 | self.quack_behavior = quack_behavior 15 | 16 | def perform_fly(self) -> None: 17 | self.fly_behavior.fly() 18 | 19 | def perform_quack(self) -> None: 20 | self.quack_behavior.quack() 21 | 22 | def display(self) -> None: 23 | raise NotImplementedError 24 | 25 | 26 | class FlyWithWings(FlyBehavior): 27 | def fly(self) -> None: 28 | print("I am using wings!") 29 | 30 | 31 | class FlyNoWay(FlyBehavior): 32 | def fly(self) -> None: 33 | print("I am not flying.") 34 | 35 | 36 | class Quack(QuackBehavior): 37 | def quack(self) -> None: 38 | print("QUACK") 39 | 40 | 41 | class Squeak(QuackBehavior): 42 | def quack(self) -> None: 43 | print("SQUEAK") 44 | 45 | 46 | class MuteQuack(QuackBehavior): 47 | def quack(self) -> None: 48 | print("") 49 | 50 | 51 | class MallardDuck(Duck): 52 | def __init__(self) -> None: 53 | super().__init__(FlyWithWings(), Quack()) 54 | 55 | def display(self) -> None: 56 | print("Looks like a mallard.") 57 | 58 | 59 | duck = MallardDuck() 60 | duck.display() 61 | duck.perform_fly() 62 | duck.perform_quack() 63 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_02_observer.py: -------------------------------------------------------------------------------- 1 | class Observer: 2 | def update(self) -> None: 3 | raise NotImplementedError 4 | 5 | 6 | class Subject: 7 | def register_observer(self, observer: Observer) -> None: 8 | raise NotImplementedError 9 | 10 | def remove_observer(self, observer: Observer) -> None: 11 | raise NotImplementedError 12 | 13 | def notify_observers(self) -> None: 14 | raise NotImplementedError 15 | 16 | 17 | class DisplayElement: 18 | def display(self) -> None: 19 | raise NotImplementedError 20 | 21 | 22 | class WeatherData(Subject): 23 | def __init__(self): 24 | self._observers = [] 25 | self.temperature = 0.0 26 | self.humidity = 0.0 27 | self.pressure = 0.0 28 | 29 | def register_observer(self, observer: Observer) -> None: 30 | self._observers.append(observer) 31 | 32 | def remove_observer(self, observer: Observer) -> None: 33 | self._observers.remove(observer) 34 | 35 | def notify_observers(self) -> None: 36 | for observer in self._observers: 37 | observer.update() 38 | 39 | def set_measurements(self, temperature: float, humidity: float, pressure: float) -> None: 40 | self.temperature = temperature 41 | self.humidity = humidity 42 | self.pressure = pressure 43 | self.notify_observers() 44 | 45 | 46 | class CurrentConditionsDisplay(Observer, DisplayElement): 47 | def __init__(self, weather_data: WeatherData): 48 | self._temperature = 0.0 49 | self._humidity = 0.0 50 | self._weather_data = weather_data 51 | self._weather_data.register_observer(self) 52 | 53 | def display(self) -> None: 54 | print(f"Current conditions: {self._temperature}°C, {self._humidity}%") 55 | 56 | def update(self) -> None: 57 | self._temperature = self._weather_data.temperature 58 | self._humidity = self._weather_data.humidity 59 | self.display() 60 | 61 | 62 | class AvgTempDisplay(Observer, DisplayElement): 63 | def __init__(self, weather_data: WeatherData): 64 | self._temperature = [] 65 | self._weather_data = weather_data 66 | self._weather_data.register_observer(self) 67 | 68 | def display(self) -> None: 69 | print(f"Average temperature: {sum(self._temperature) / len(self._temperature)}°C") 70 | 71 | def update(self) -> None: 72 | self._temperature.append(self._weather_data.temperature) 73 | self.display() 74 | 75 | 76 | weather_data = WeatherData() 77 | current_display = CurrentConditionsDisplay(weather_data) 78 | forecast_display = AvgTempDisplay(weather_data) 79 | 80 | weather_data.set_measurements(23.0, 68.1, 1018.0) 81 | weather_data.set_measurements(24.2, 70.4, 1019.2) 82 | weather_data.set_measurements(25.8, 71.2, 1018.4) 83 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_03_decorator.py: -------------------------------------------------------------------------------- 1 | class Beverage: 2 | @property 3 | def description(self) -> str: 4 | return self.__class__.__name__ 5 | 6 | @property 7 | def cost(self) -> float: 8 | raise NotImplementedError 9 | 10 | 11 | class CondimentDecorator(Beverage): 12 | def __init__(self, beverage: Beverage): 13 | self._beverage = beverage 14 | 15 | @property 16 | def description(self) -> str: 17 | return f"{self._beverage.description}, {super(CondimentDecorator, self).description}" 18 | 19 | @property 20 | def cost(self) -> float: 21 | raise NotImplementedError 22 | 23 | 24 | class Espresso(Beverage): 25 | @property 26 | def cost(self) -> float: 27 | return 1.99 28 | 29 | 30 | class HouseBlend(Beverage): 31 | @property 32 | def cost(self) -> float: 33 | return 0.89 34 | 35 | 36 | class Mocha(CondimentDecorator): 37 | @property 38 | def cost(self) -> float: 39 | return self._beverage.cost + 0.20 40 | 41 | 42 | class Soy(CondimentDecorator): 43 | @property 44 | def cost(self) -> float: 45 | return self._beverage.cost + 0.15 46 | 47 | 48 | beverage = Espresso() 49 | beverage = Mocha(beverage) 50 | beverage = Mocha(beverage) 51 | beverage = Soy(beverage) 52 | print(f"${beverage.cost} for '{beverage.description}'") 53 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_04_factory.py: -------------------------------------------------------------------------------- 1 | class Ingredient: 2 | def __init__(self): 3 | print(self.__class__.__name__) 4 | 5 | 6 | class ThinCrustDough(Ingredient): 7 | pass 8 | 9 | 10 | class ThickCrustDough(Ingredient): 11 | pass 12 | 13 | 14 | class MarinaraSauce(Ingredient): 15 | pass 16 | 17 | 18 | class PlumTomatoSauce(Ingredient): 19 | pass 20 | 21 | 22 | class MozzarellaCheese(Ingredient): 23 | pass 24 | 25 | 26 | class ReggianoCheese(Ingredient): 27 | pass 28 | 29 | 30 | class Garlic(Ingredient): 31 | pass 32 | 33 | 34 | class Onion(Ingredient): 35 | pass 36 | 37 | 38 | class Mushroom(Ingredient): 39 | pass 40 | 41 | 42 | class SlicedPepperoni(Ingredient): 43 | pass 44 | 45 | 46 | class FreshClams(Ingredient): 47 | pass 48 | 49 | 50 | class FrozenClams(Ingredient): 51 | pass 52 | 53 | 54 | class PizzaIngredientFactory: 55 | def create_dough(self): 56 | raise NotImplementedError 57 | 58 | def create_sauce(self): 59 | raise NotImplementedError 60 | 61 | def create_cheese(self): 62 | raise NotImplementedError 63 | 64 | def create_veggies(self): 65 | raise NotImplementedError 66 | 67 | def create_pepperoni(self): 68 | raise NotImplementedError 69 | 70 | def create_clam(self): 71 | raise NotImplementedError 72 | 73 | 74 | class NYPizzaIngredientFactory(PizzaIngredientFactory): 75 | def create_dough(self): 76 | return ThinCrustDough() 77 | 78 | def create_sauce(self): 79 | return MarinaraSauce() 80 | 81 | def create_cheese(self): 82 | return ReggianoCheese() 83 | 84 | def create_veggies(self): 85 | return [Garlic(), Onion()] 86 | 87 | def create_pepperoni(self): 88 | return SlicedPepperoni() 89 | 90 | def create_clam(self): 91 | return FreshClams() 92 | 93 | 94 | class ChicagoPizzaIngredientFactory(PizzaIngredientFactory): 95 | def create_dough(self): 96 | return ThickCrustDough() 97 | 98 | def create_sauce(self): 99 | return PlumTomatoSauce() 100 | 101 | def create_cheese(self): 102 | return MozzarellaCheese() 103 | 104 | def create_veggies(self): 105 | return [Garlic(), Mushroom()] 106 | 107 | def create_pepperoni(self): 108 | return SlicedPepperoni() 109 | 110 | def create_clam(self): 111 | return FrozenClams() 112 | 113 | 114 | class Pizza: 115 | name = ... 116 | 117 | def __init__(self, ingredient_factory: PizzaIngredientFactory): 118 | self._ingredient_factory = ingredient_factory 119 | 120 | def prepare(self) -> None: 121 | raise NotImplementedError 122 | 123 | def bake(self) -> None: 124 | print("Bake for 25 minutes at 350") 125 | 126 | def cut(self) -> None: 127 | print("Cutting the pizza into diagonal slices") 128 | 129 | def box(self) -> None: 130 | print("Place the pizza in official PizzaStore box") 131 | 132 | 133 | class CheesePizza(Pizza): 134 | def prepare(self) -> None: 135 | print(f"Preparing {self.name}") 136 | self._ingredient_factory.create_dough() 137 | self._ingredient_factory.create_sauce() 138 | self._ingredient_factory.create_cheese() 139 | 140 | 141 | class ClamPizza(Pizza): 142 | def prepare(self) -> None: 143 | print(f"Preparing {self.name}") 144 | self._ingredient_factory.create_dough() 145 | self._ingredient_factory.create_sauce() 146 | self._ingredient_factory.create_cheese() 147 | self._ingredient_factory.create_clam() 148 | 149 | 150 | class PizzaStore: 151 | def order_pizza(self, pizza_type: str) -> Pizza: 152 | pizza = self.create_pizza(pizza_type) 153 | 154 | pizza.prepare() 155 | pizza.bake() 156 | pizza.cut() 157 | pizza.box() 158 | 159 | return pizza 160 | 161 | # Factory Method: 162 | def create_pizza(self, pizza_type: str) -> Pizza: 163 | raise NotImplementedError 164 | 165 | 166 | class NYPizzaStore(PizzaStore): 167 | def create_pizza(self, pizza_type: str) -> Pizza: 168 | ingredient_factory = NYPizzaIngredientFactory() 169 | 170 | match pizza_type: 171 | case "cheese": 172 | pizza = CheesePizza(ingredient_factory) 173 | pizza.name = "NY Style Sauce and Cheese Pizza" 174 | case "clam": 175 | pizza = ClamPizza(ingredient_factory) 176 | pizza.name = "NY Style Sauce and Clam Pizza" 177 | case _: 178 | raise RuntimeError("Unknown pizza type") 179 | 180 | return pizza 181 | 182 | 183 | class ChicagoPizzaStore(PizzaStore): 184 | def create_pizza(self, pizza_type: str) -> Pizza: 185 | ingredient_factory = ChicagoPizzaIngredientFactory() 186 | 187 | match pizza_type: 188 | case "cheese": 189 | pizza = CheesePizza(ingredient_factory) 190 | pizza.name = "Chicago Style Deep Dish Cheese Pizza" 191 | case "clam": 192 | pizza = ClamPizza(ingredient_factory) 193 | pizza.name = "Chicago Style Deep Dish Clam Pizza" 194 | case _: 195 | raise RuntimeError("Unknown pizza type") 196 | 197 | return pizza 198 | 199 | 200 | ny_store = NYPizzaStore() 201 | ny_store.order_pizza("cheese") 202 | 203 | chicago_store = ChicagoPizzaStore() 204 | chicago_store.order_pizza("cheese") 205 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_05_singleton.py: -------------------------------------------------------------------------------- 1 | class ChocolateBoiler: 2 | _instance = None 3 | 4 | def __new__(cls): 5 | if not cls._instance: 6 | cls._instance = super(ChocolateBoiler, cls).__new__(cls) 7 | return cls._instance 8 | 9 | 10 | boiler_0 = ChocolateBoiler() 11 | boiler_1 = ChocolateBoiler() 12 | 13 | print(f"#0: {boiler_0}") 14 | print(f"#1: {boiler_1}") 15 | print(f"Are they the same object? {boiler_0 is boiler_1}") 16 | 17 | 18 | # Implementation using variable - instantiated on module import: 19 | class ChocolateBoiler: 20 | pass 21 | 22 | 23 | chocolate_boiler = ChocolateBoiler() 24 | print(f"Are they the same object? {chocolate_boiler is chocolate_boiler}") 25 | 26 | 27 | # Implementation using function - using 'attr': 28 | def get_chocolate_boiler() -> ChocolateBoiler: 29 | if not hasattr(get_chocolate_boiler, "instance"): 30 | setattr(get_chocolate_boiler, "instance", ChocolateBoiler()) 31 | return getattr(get_chocolate_boiler, "instance") 32 | 33 | 34 | print(f"Are they the same object? {get_chocolate_boiler() is get_chocolate_boiler()}") 35 | 36 | # Implementation using function - using variable: 37 | _chocolate_boiler = None 38 | 39 | 40 | def get_chocolate_boiler() -> ChocolateBoiler: 41 | global _chocolate_boiler 42 | 43 | if not _chocolate_boiler: 44 | _chocolate_boiler = ChocolateBoiler() 45 | 46 | return _chocolate_boiler 47 | 48 | 49 | print(f"Are they the same object? {get_chocolate_boiler() is get_chocolate_boiler()}") 50 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_06_command.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | class Device: 5 | @property 6 | def name(self) -> str: 7 | return self.__class__.__name__ 8 | 9 | def on(self) -> None: 10 | print(f"{self.name} was turned on") 11 | 12 | def off(self) -> None: 13 | print(f"{self.name} was turned off") 14 | 15 | 16 | class Light(Device): 17 | pass 18 | 19 | 20 | class Tv(Device): 21 | pass 22 | 23 | 24 | class Stereo(Device): 25 | def __init__(self) -> None: 26 | self.volume = 0 27 | 28 | def set_cd(self) -> None: 29 | print(f"{self.name} CD set") 30 | 31 | def set_volume(self, volume: int) -> None: 32 | print(f"{self.name} Volume set to {volume}") 33 | self.volume = volume 34 | 35 | 36 | class Command: 37 | def execute(self) -> None: 38 | raise NotImplementedError 39 | 40 | def undo(self) -> None: 41 | raise NotImplementedError 42 | 43 | 44 | class NoCommand(Command): 45 | def execute(self) -> None: 46 | pass 47 | 48 | def undo(self) -> None: 49 | pass 50 | 51 | 52 | class MarcoCommand(Command): 53 | def __init__(self, commands: List[Command]): 54 | self._commands = commands 55 | 56 | def execute(self) -> None: 57 | for command in self._commands: 58 | command.execute() 59 | 60 | def undo(self) -> None: 61 | for command in self._commands[::-1]: 62 | command.undo() 63 | 64 | 65 | class DeviceOnCommand(Command): 66 | def __init__(self, device: Device) -> None: 67 | self._device = device 68 | 69 | def execute(self) -> None: 70 | self._device.on() 71 | 72 | def undo(self) -> None: 73 | self._device.off() 74 | 75 | 76 | class DeviceOffCommand(Command): 77 | def __init__(self, device: Device) -> None: 78 | self._device = device 79 | 80 | def execute(self) -> None: 81 | self._device.off() 82 | 83 | def undo(self) -> None: 84 | self._device.on() 85 | 86 | 87 | class StereoVolumeUpCommand(Command): 88 | def __init__(self, stereo: Stereo) -> None: 89 | self._stereo = stereo 90 | 91 | def execute(self) -> None: 92 | self._stereo.set_volume(stereo.volume + 1) 93 | 94 | def undo(self) -> None: 95 | self._stereo.set_volume(stereo.volume - 1) 96 | 97 | 98 | class RemoteControl: 99 | def __init__(self): 100 | self._on_commands = [NoCommand()] * 7 101 | self._off_commands = [NoCommand()] * 7 102 | self._undo_commands = [] 103 | 104 | def set_command(self, slot: int, on_command: Command, off_command: Command) -> None: 105 | self._on_commands[slot] = on_command 106 | self._off_commands[slot] = off_command 107 | 108 | def on_button_pushed(self, slot: int) -> None: 109 | self._on_commands[slot].execute() 110 | self._undo_commands.append(self._on_commands[slot]) 111 | 112 | def off_button_pushed(self, slot: int) -> None: 113 | self._off_commands[slot].execute() 114 | self._undo_commands.append(self._off_commands[slot]) 115 | 116 | def undo_button_pushed(self) -> None: 117 | if not self._undo_commands: 118 | return 119 | self._undo_commands.pop().undo() 120 | 121 | 122 | light = Light() 123 | tv = Tv() 124 | stereo = Stereo() 125 | 126 | light_on_command, light_off_command = DeviceOnCommand(light), DeviceOffCommand(light) 127 | tv_on_command, tv_off_command = DeviceOnCommand(tv), DeviceOffCommand(tv) 128 | stereo_on_command, stereo_off_command = DeviceOnCommand(stereo), DeviceOffCommand(stereo) 129 | 130 | volume_up_command = StereoVolumeUpCommand(stereo) 131 | 132 | party_on_command = MarcoCommand([light_on_command, tv_on_command, stereo_on_command, volume_up_command]) 133 | party_off_command = MarcoCommand([light_on_command, tv_on_command, stereo_off_command]) 134 | 135 | remote = RemoteControl() 136 | remote.set_command(0, light_on_command, light_off_command) 137 | remote.set_command(1, tv_on_command, tv_off_command) 138 | remote.set_command(2, stereo_on_command, stereo_off_command) 139 | remote.set_command(3, party_on_command, party_off_command) 140 | 141 | remote.on_button_pushed(1) 142 | remote.on_button_pushed(3) 143 | remote.undo_button_pushed() 144 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_07_adapter.py: -------------------------------------------------------------------------------- 1 | class Duck: 2 | def quack(self) -> None: 3 | raise NotImplementedError 4 | 5 | def fly(self) -> None: 6 | raise NotImplementedError 7 | 8 | 9 | class Turkey: 10 | def gobble(self) -> None: 11 | raise NotImplementedError 12 | 13 | def fly(self) -> None: 14 | raise NotImplementedError 15 | 16 | 17 | class WildTurkey(Turkey): 18 | def gobble(self) -> None: 19 | print("Gobble Gobble") 20 | 21 | def fly(self) -> None: 22 | print("I am flying a short distance") 23 | 24 | 25 | class TurkeyAdapter(Duck): 26 | def __init__(self, turkey: Turkey): 27 | self._turkey = turkey 28 | 29 | def quack(self) -> None: 30 | self._turkey.gobble() 31 | 32 | def fly(self) -> None: 33 | self._turkey.fly() 34 | 35 | 36 | # We ran out of ducks, so we use turkeys: 37 | turkey = WildTurkey() 38 | turkey_adapter = TurkeyAdapter(turkey) 39 | 40 | turkey_adapter.quack() 41 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_07_facade.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock 2 | 3 | 4 | class HomeTheaterFacade: 5 | def __init__(self, amplifier, tuner, projector, lights, screen, player, popper): 6 | self._amplifier = amplifier 7 | self._tuner = tuner 8 | self._projector = projector 9 | self._lights = lights 10 | self._screen = screen 11 | self._player = player 12 | self._popper = popper 13 | 14 | # Wrap complex behavior into single method: 15 | def watch_movie(self, movie): 16 | self._popper.on() 17 | self._popper.pop() 18 | 19 | self._lights.dim(10) 20 | 21 | self._screen.down() 22 | 23 | self._projector.on() 24 | 25 | self._amplifier.on() 26 | self._amplifier.set_volume(20) 27 | 28 | self._player.on() 29 | self._player.play(movie) 30 | 31 | 32 | home_theater = HomeTheaterFacade(*([Mock()] * 7)) 33 | home_theater.watch_movie("Joker") 34 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_08_template_method.py: -------------------------------------------------------------------------------- 1 | class CaffeineBeverage: 2 | def prepare_recipe(self) -> None: 3 | self._boil_water() 4 | self._brew() 5 | self._pour_in_cup() 6 | self._add_condiments() 7 | 8 | def _boil_water(self) -> None: 9 | print("Boiling water") 10 | 11 | def _pour_in_cup(self) -> None: 12 | print("Pouring in a cup") 13 | 14 | def _brew(self) -> None: 15 | raise NotImplementedError 16 | 17 | def _add_condiments(self) -> None: 18 | raise NotImplementedError 19 | 20 | 21 | class Tea(CaffeineBeverage): 22 | def _brew(self) -> None: 23 | print("Steeping the tea") 24 | 25 | def _add_condiments(self) -> None: 26 | print("Adding Lemon") 27 | 28 | 29 | class Coffee(CaffeineBeverage): 30 | def _brew(self) -> None: 31 | print("Dripping Coffee through filter") 32 | 33 | def _add_condiments(self) -> None: 34 | print("Adding Sugar and Milk") 35 | 36 | 37 | Coffee().prepare_recipe() 38 | Tea().prepare_recipe() 39 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_09_composite.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC 4 | from dataclasses import dataclass 5 | 6 | 7 | class MenuComponent: 8 | def add(self, menu_component: MenuComponent): 9 | raise NotImplementedError 10 | 11 | def remove(self, menu_component: MenuComponent): 12 | raise NotImplementedError 13 | 14 | def get_child(self, i: int): 15 | raise NotImplementedError 16 | 17 | def print(self): 18 | raise NotImplementedError 19 | 20 | 21 | @dataclass 22 | class MenuItem(MenuComponent, ABC): 23 | name: str 24 | description: str 25 | vegetarian: bool 26 | price: float 27 | 28 | def print(self): 29 | print(f"{self.name}, {self.price}, {self.description}") 30 | 31 | 32 | class Menu(MenuComponent): 33 | def __init__(self, name: str): 34 | self._name = name 35 | self._menu_components = [] 36 | 37 | def add(self, menu_component: MenuComponent): 38 | self._menu_components.append(menu_component) 39 | 40 | def remove(self, menu_component: MenuComponent): 41 | self._menu_components.remove(menu_component) 42 | 43 | def get_child(self, i: int): 44 | return self._menu_components[i] 45 | 46 | def print(self): 47 | print(self._name) 48 | for menu_component in self._menu_components: 49 | menu_component.print() 50 | 51 | 52 | class Waitress: 53 | def __init__(self, menu_component: MenuComponent): 54 | self._menu_component = menu_component 55 | 56 | def print_menu(self): 57 | self._menu_component.print() 58 | 59 | 60 | breakfast_menu = Menu("BREAKFAST") 61 | dinner_menu = Menu("DINNER") 62 | dessert_menu = Menu("DESSERT") 63 | 64 | all_menus = Menu("ALL MENUS") 65 | all_menus.add(breakfast_menu) 66 | all_menus.add(dinner_menu) 67 | 68 | dinner_menu.add(MenuItem("Pasta", "Pasta with marinara Sauce", True, 3.89)) 69 | dinner_menu.add(dessert_menu) 70 | 71 | dessert_menu.add(MenuItem("Apple Pie", "Apple pie with a flaky crust, topped with vanilla ice cream", True, 1.59)) 72 | 73 | Waitress(all_menus).print_menu() 74 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_09_iterator.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterator 2 | from dataclasses import dataclass 3 | from typing import ( 4 | Dict, 5 | List, 6 | Union, 7 | ) 8 | 9 | 10 | @dataclass 11 | class MenuItem: 12 | name: str 13 | description: str 14 | vegetarian: bool 15 | price: float 16 | 17 | 18 | class DinnerMenuIterator(Iterator): 19 | # Just for demonstration purposes! 20 | def __init__(self, collection: List[MenuItem]): 21 | self._collection = collection 22 | self._position = 0 23 | 24 | def __next__(self) -> MenuItem: 25 | try: 26 | value = self._collection[self._position] 27 | self._position += 1 28 | except IndexError: 29 | raise StopIteration() 30 | 31 | return value 32 | 33 | 34 | class DinnerMenu: 35 | # Just for demonstration purposes! 36 | menu = [ 37 | MenuItem("Vegetarian BLT", "Fake Bacon with lettuce on whole wheat", True, 2.99), 38 | MenuItem("BLT", "Bacon with lettuce on whole wheat", False, 2.99), 39 | MenuItem("Soup of the day", "Soup of the day, with a side of potato salad", False, 3.99), 40 | MenuItem("HotDog", "A Hot Dog with sauerkraut, relish, onions, topped with cheese", False, 3.05), 41 | ] 42 | 43 | def __iter__(self) -> DinnerMenuIterator: 44 | # Factory Method 45 | return DinnerMenuIterator(self.menu) 46 | 47 | 48 | class BreakfastMenuIterator(Iterator): 49 | # Just for demonstration purposes! 50 | def __init__(self, collection: Dict[str, MenuItem]): 51 | self._collection = collection 52 | self._position = 0 53 | 54 | def __next__(self) -> MenuItem: 55 | try: 56 | value = list(self._collection.values())[self._position] 57 | self._position += 1 58 | except IndexError: 59 | raise StopIteration() 60 | 61 | return value 62 | 63 | 64 | class BreakfastMenu: 65 | # Just for demonstration purposes! 66 | menu = { 67 | "K&B's Pancake Breakfast": MenuItem("K&B's Pancake Breakfast", "Pancakes with scrambled eggs and toast", True, 2.99), 68 | "Regular Pancake Breakfast": MenuItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", False, 2.99), 69 | "Blueberry Pancakes": MenuItem("Blueberry Pancakes", "Pancakes made with fresh blueberries", True, 3.49), 70 | } 71 | 72 | def __iter__(self) -> BreakfastMenuIterator: 73 | # Factory Method 74 | return BreakfastMenuIterator(self.menu) 75 | 76 | 77 | class Waitress: 78 | def __init__(self, pancake_menu: BreakfastMenu, dinner_menu: DinnerMenu): 79 | self._pancake_menu = pancake_menu 80 | self._dinner_menu = dinner_menu 81 | 82 | def print_menu(self): 83 | print("BREAKFAST") 84 | self._print_menu(self._pancake_menu) 85 | print("DINNER") 86 | self._print_menu(self._dinner_menu) 87 | 88 | @staticmethod 89 | def _print_menu(menu: Union[BreakfastMenu, DinnerMenu]): 90 | for menu_item in menu: 91 | print(f"{menu_item.name}, {menu_item.price}, {menu_item.description}") 92 | 93 | 94 | Waitress(BreakfastMenu(), DinnerMenu()).print_menu() 95 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_10_state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from random import random 4 | 5 | 6 | class State: 7 | def __init__(self, gumball_machine: GumballMachine): 8 | self._gumball_machine = gumball_machine 9 | 10 | def insert_quarter(self) -> None: 11 | pass 12 | 13 | def eject_quarter(self) -> None: 14 | pass 15 | 16 | def turn_crank(self) -> None: 17 | pass 18 | 19 | def dispense(self) -> None: 20 | pass 21 | 22 | 23 | class NoQuarterState(State): 24 | def insert_quarter(self) -> None: 25 | print("You inserted a quarter") 26 | self._gumball_machine.state = self._gumball_machine.has_quarter_state 27 | 28 | 29 | class HasQuarterState(State): 30 | def eject_quarter(self) -> None: 31 | print("Quarter returned") 32 | self._gumball_machine.state = self._gumball_machine.no_quarter_state 33 | 34 | def turn_crank(self) -> None: 35 | print("You turned...") 36 | 37 | if random() < 0.1 and self._gumball_machine.count > 1: 38 | self._gumball_machine.state = self._gumball_machine.winner_state 39 | else: 40 | self._gumball_machine.state = self._gumball_machine.sold_state 41 | 42 | 43 | class SoldState(State): 44 | def dispense(self) -> None: 45 | self._gumball_machine.release_ball() 46 | 47 | if self._gumball_machine.count > 0: 48 | self._gumball_machine.state = self._gumball_machine.no_quarter_state 49 | else: 50 | print("Out of gumballs!") 51 | self._gumball_machine.state = self._gumball_machine.sold_out_state 52 | 53 | 54 | class SoldOutState(State): 55 | pass 56 | 57 | 58 | class WinnerState(State): 59 | def dispense(self) -> None: 60 | self._gumball_machine.release_ball() 61 | 62 | if self._gumball_machine.count == 0: 63 | self._gumball_machine.state = self._gumball_machine.sold_out_state 64 | else: 65 | self._gumball_machine.release_ball() 66 | print("You are a WINNER!") 67 | 68 | if self._gumball_machine.count > 0: 69 | self._gumball_machine.state = self._gumball_machine.no_quarter_state 70 | else: 71 | print("Out of gumballs!") 72 | self._gumball_machine.state = self._gumball_machine.sold_out_state 73 | 74 | 75 | class GumballMachine: 76 | def __init__(self, count: int): 77 | self.count = count 78 | 79 | self.no_quarter_state = NoQuarterState(self) 80 | self.has_quarter_state = HasQuarterState(self) 81 | self.sold_state = SoldState(self) 82 | self.sold_out_state = SoldOutState(self) 83 | self.winner_state = WinnerState(self) 84 | 85 | self.state = self.no_quarter_state if count > 0 else self.sold_out_state 86 | 87 | def insert_quarter(self) -> None: 88 | self.state.insert_quarter() 89 | 90 | def eject_quarter(self) -> None: 91 | self.state.eject_quarter() 92 | 93 | def turn_crank(self) -> None: 94 | self.state.turn_crank() 95 | self.state.dispense() 96 | 97 | def release_ball(self) -> None: 98 | print("A ball rolling out the slot...") 99 | if self.count > 0: 100 | self.count = self.count - 1 101 | 102 | 103 | machine = GumballMachine(5) 104 | 105 | machine.insert_quarter() 106 | machine.turn_crank() 107 | 108 | machine.insert_quarter() 109 | machine.turn_crank() 110 | 111 | machine.insert_quarter() 112 | machine.turn_crank() 113 | -------------------------------------------------------------------------------- /books/head-first-design-patterns/ch_11_virtual_proxy.py: -------------------------------------------------------------------------------- 1 | class Icon: 2 | @property 3 | def width(self) -> int: 4 | raise NotImplementedError 5 | 6 | @property 7 | def height(self) -> int: 8 | raise NotImplementedError 9 | 10 | def paint_icon(self) -> None: 11 | raise NotImplementedError 12 | 13 | 14 | class ImageIcon(Icon): 15 | @property 16 | def width(self) -> int: 17 | return 1280 18 | 19 | @property 20 | def height(self) -> int: 21 | return 720 22 | 23 | def paint_icon(self) -> None: 24 | print(":)") 25 | 26 | 27 | class ImageProxy(Icon): 28 | def __init__(self, url: str): 29 | self._image_icon = None 30 | self._url = url 31 | 32 | # Following 'if' statements can be reworked to use The State Pattern: ImageNotLoaded and ImageLoaded 33 | @property 34 | def width(self) -> int: 35 | return self._image_icon.width if self._image_icon else 600 36 | 37 | @property 38 | def height(self) -> int: 39 | return self._image_icon.height if self._image_icon else 800 40 | 41 | def paint_icon(self) -> None: 42 | if not self._image_icon: 43 | # Download image from the internet 44 | print(f"Downloading the image from '{self._url}'") 45 | self._image_icon = ImageIcon() 46 | self._image_icon.paint_icon() 47 | 48 | 49 | image = ImageProxy("whatever://image") 50 | image.paint_icon() 51 | -------------------------------------------------------------------------------- /books/kubernetes-in-action.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Kubernetes in Action, Second Edition 4 | 5 | Book by Marko Lukša 6 | -------------------------------------------------------------------------------- /books/pytest/.coveragerc: -------------------------------------------------------------------------------- 1 | [paths] 2 | source = 3 | src/ -------------------------------------------------------------------------------- /books/pytest/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10.2 2 | 3 | WORKDIR /src 4 | 5 | ENV PYTHONPATH "${PYTHONPATH}:/src" 6 | 7 | COPY requirements.txt . 8 | COPY setup.cfg . 9 | 10 | RUN pip install -r requirements.txt 11 | 12 | COPY src/ src/ 13 | COPY tests/ tests/ 14 | -------------------------------------------------------------------------------- /books/pytest/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | book: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | volumes: 8 | - ./:/src 9 | -------------------------------------------------------------------------------- /books/pytest/requirements.txt: -------------------------------------------------------------------------------- 1 | tinydb 2 | pytest 3 | faker 4 | tox 5 | coverage 6 | pytest-cov 7 | tinydb 8 | typer 9 | rich 10 | -------------------------------------------------------------------------------- /books/pytest/setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | python_paths = . 3 | testpaths = tests 4 | -------------------------------------------------------------------------------- /books/pytest/src/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for cards.""" 2 | 3 | __version__ = "1.0.0" 4 | 5 | from .api import * # noqa 6 | from .cli import app # noqa 7 | -------------------------------------------------------------------------------- /books/pytest/src/api.py: -------------------------------------------------------------------------------- 1 | """ 2 | API for the cards project 3 | """ 4 | from dataclasses import asdict 5 | from dataclasses import dataclass 6 | from dataclasses import field 7 | 8 | from src.db import DB 9 | 10 | __all__ = [ 11 | "Card", 12 | "CardsDB", 13 | "CardsException", 14 | "MissingSummary", 15 | "InvalidCardId", 16 | ] 17 | 18 | __version__ = "1.0.0" 19 | 20 | 21 | @dataclass 22 | class Card: 23 | summary: str = None 24 | owner: str = None 25 | state: str = "todo" 26 | id: int = field(default=None, compare=False) 27 | 28 | @classmethod 29 | def from_dict(cls, d): 30 | return Card(**d) 31 | 32 | def to_dict(self): 33 | return asdict(self) 34 | 35 | 36 | class CardsException(Exception): 37 | pass 38 | 39 | 40 | class MissingSummary(CardsException): 41 | pass 42 | 43 | 44 | class InvalidCardId(CardsException): 45 | pass 46 | 47 | 48 | class CardsDB: 49 | def __init__(self, db_path): 50 | self._db_path = db_path 51 | self._db = DB(db_path, ".cards_db") 52 | 53 | def add_card(self, card: Card) -> int: 54 | """Add a card, return the id of card.""" 55 | if not card.summary: 56 | raise MissingSummary 57 | if card.owner is None: 58 | card.owner = "" 59 | id = self._db.create(card.to_dict()) 60 | self._db.update(id, {"id": id}) 61 | return id 62 | 63 | def get_card(self, card_id: int) -> Card: 64 | """Return a card with a matching id.""" 65 | db_item = self._db.read(card_id) 66 | if db_item is not None: 67 | return Card.from_dict(db_item) 68 | else: 69 | raise InvalidCardId(card_id) 70 | 71 | def list_cards(self, owner=None, state=None): 72 | """Return a list of cards.""" 73 | all = self._db.read_all() 74 | if (owner is not None) and (state is not None): 75 | return [ 76 | Card.from_dict(t) 77 | for t in all 78 | if (t["owner"] == owner and t["state"] == state) 79 | ] 80 | elif owner is not None: 81 | return [ 82 | Card.from_dict(t) for t in all if t["owner"] == owner 83 | ] 84 | elif state is not None: 85 | return [ 86 | Card.from_dict(t) for t in all if t["state"] == state 87 | ] 88 | else: 89 | return [Card.from_dict(t) for t in all] 90 | z 91 | def count(self) -> int: 92 | """Return the number of cards in db.""" 93 | return self._db.count() 94 | 95 | def update_card(self, card_id: int, card_mods: Card) -> None: 96 | """Update a card with modifications.""" 97 | try: 98 | self._db.update(card_id, card_mods.to_dict()) 99 | except KeyError as exc: 100 | raise InvalidCardId(card_id) from exc 101 | 102 | def start(self, card_id: int): 103 | """Set a card state to 'in prog'.""" 104 | self.update_card(card_id, Card(state="in prog")) 105 | 106 | def finish(self, card_id: int): 107 | """Set a card state to 'done'.""" 108 | self.update_card(card_id, Card(state="done")) 109 | 110 | def delete_card(self, card_id: int) -> None: 111 | """Remove a card from db with given card_id.""" 112 | try: 113 | self._db.delete(card_id) 114 | except KeyError as exc: 115 | raise InvalidCardId(card_id) from exc 116 | 117 | def delete_all(self) -> None: 118 | """Remove all cards from db.""" 119 | self._db.delete_all() 120 | 121 | def close(self): 122 | self._db.close() 123 | 124 | def path(self): 125 | return self._db_path 126 | -------------------------------------------------------------------------------- /books/pytest/src/cli.py: -------------------------------------------------------------------------------- 1 | """Command Line Interface (CLI) for cards project.""" 2 | import os 3 | import pathlib 4 | from contextlib import contextmanager 5 | from io import StringIO 6 | from typing import List 7 | 8 | import rich 9 | import typer 10 | from rich.table import Table 11 | 12 | import src.api as cards 13 | 14 | app = typer.Typer(name="cards", add_completion=False) 15 | 16 | 17 | @app.command() 18 | def version(): 19 | """Return version of cards application""" 20 | print(cards.__version__) 21 | 22 | 23 | @app.command() 24 | def add( 25 | summary: List[str], owner: str = typer.Option(None, "-o", "--owner") 26 | ): 27 | """Add a card to db.""" 28 | summary = " ".join(summary) if summary else None 29 | with cards_db() as db: 30 | db.add_card(cards.Card(summary, owner, state="todo")) 31 | 32 | 33 | @app.command() 34 | def delete(card_id: int): 35 | """Remove card in db with given id.""" 36 | with cards_db() as db: 37 | try: 38 | db.delete_card(card_id) 39 | except cards.InvalidCardId: 40 | print(f"Error: Invalid card id {card_id}") 41 | 42 | 43 | @app.command("list") 44 | def list_cards( 45 | owner: str = typer.Option(None, "-o", "--owner"), 46 | state: str = typer.Option(None, "-s", "--state"), 47 | ): 48 | """ 49 | List cards in db. 50 | """ 51 | with cards_db() as db: 52 | the_cards = db.list_cards(owner=owner, state=state) 53 | table = Table(box=rich.box.SIMPLE) 54 | table.add_column("ID") 55 | table.add_column("state") 56 | table.add_column("owner") 57 | table.add_column("summary") 58 | for t in the_cards: 59 | owner = "" if t.owner is None else t.owner 60 | table.add_row(str(t.id), t.state, owner, t.summary) 61 | out = StringIO() 62 | rich.print(table, file=out) 63 | print(out.getvalue()) 64 | 65 | 66 | @app.command() 67 | def update( 68 | card_id: int, 69 | owner: str = typer.Option(None, "-o", "--owner"), 70 | summary: List[str] = typer.Option(None, "-s", "--summary"), 71 | ): 72 | """Modify a card in db with given id with new info.""" 73 | summary = " ".join(summary) if summary else None 74 | with cards_db() as db: 75 | try: 76 | db.update_card( 77 | card_id, cards.Card(summary, owner, state=None) 78 | ) 79 | except cards.InvalidCardId: 80 | print(f"Error: Invalid card id {card_id}") 81 | 82 | 83 | @app.command() 84 | def start(card_id: int): 85 | """Set a card state to 'in prog'.""" 86 | with cards_db() as db: 87 | try: 88 | db.start(card_id) 89 | except cards.InvalidCardId: 90 | print(f"Error: Invalid card id {card_id}") 91 | 92 | 93 | @app.command() 94 | def finish(card_id: int): 95 | """Set a card state to 'done'.""" 96 | with cards_db() as db: 97 | try: 98 | db.finish(card_id) 99 | except cards.InvalidCardId: 100 | print(f"Error: Invalid card id {card_id}") 101 | 102 | 103 | @app.command() 104 | def config(): 105 | """List the path to the Cards db.""" 106 | with cards_dbz() as db: 107 | print(db.path()) 108 | 109 | 110 | @app.command() 111 | def count(): 112 | """Return number of cards in db.""" 113 | with cards_db() as db: 114 | print(db.count()) 115 | 116 | 117 | @app.callback(invoke_without_command=True) 118 | def main(ctx: typer.Context): 119 | """ 120 | Cards is a small command line task tracking application. 121 | """ 122 | if ctx.invoked_subcommand is None: 123 | list_cards(owner=None, state=None) 124 | 125 | 126 | def get_path(): 127 | db_path_env = os.getenv("CARDS_DB_DIR", "") 128 | if db_path_env: 129 | db_path = pathlib.Path(db_path_env) 130 | else: 131 | db_path = pathlib.Path.home() / "cards_db" 132 | return db_path 133 | 134 | 135 | @contextmanager 136 | def cards_db(): 137 | db_path = get_path() 138 | db = cards.CardsDB(db_path) 139 | yield db 140 | db.close() 141 | -------------------------------------------------------------------------------- /books/pytest/src/db.py: -------------------------------------------------------------------------------- 1 | """ 2 | DB for the cards project 3 | """ 4 | import tinydb 5 | 6 | 7 | class DB: 8 | def __init__(self, db_path, db_file_prefix): 9 | self._db = tinydb.TinyDB( 10 | db_path / f"{db_file_prefix}.json", create_dirs=True 11 | ) 12 | 13 | def create(self, item: dict) -> int: 14 | id = self._db.insert(item) 15 | return id 16 | 17 | def read(self, id: int): 18 | item = self._db.get(doc_id=id) 19 | return item 20 | 21 | def read_all(self): 22 | return self._db 23 | 24 | def update(self, id: int, mods) -> None: 25 | changes = {k: v for k, v in mods.items() if v is not None} 26 | self._db.update(changes, doc_ids=[id]) 27 | 28 | def delete(self, id: int) -> None: 29 | self._db.remove(doc_ids=[id]) 30 | 31 | def delete_all(self) -> None: 32 | self._db.truncate() 33 | 34 | def count(self) -> int: 35 | return len(self._db) 36 | 37 | def close(self): 38 | self._db.close() 39 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_02/test_card.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from src import Card 4 | 5 | 6 | def test_field_access(): 7 | c = Card("something", "brian", "todo", 123) 8 | assert (c.summary, c.owner, c.state, c.id) == ("something", "brian", "todo", 123) 9 | 10 | 11 | def test_defaults(): 12 | c = Card() 13 | assert (c.summary, c.owner, c.state, c.id) == (None, None, "todo", None) 14 | 15 | 16 | def test_equality(): 17 | assert Card("something", "brian", "todo", 123) == Card("something", "brian", "todo", 123) 18 | 19 | 20 | def test_equality_with_different_ids(): 21 | assert Card("something", "brian", "todo", 123) == Card("something", "brian", "todo", 321) 22 | 23 | 24 | def test_inequality(): 25 | assert Card("something", "brian", "todo", 123) != Card("completely different", "okken", "todo", 123) 26 | 27 | 28 | def test_to_dict(): 29 | assert Card.from_dict({ 30 | "summary": "something", 31 | "owner": "brian", 32 | "state": "todo", 33 | "id": 123 34 | }) == Card("something", "brian", "todo", 123) 35 | 36 | 37 | def test_from_dict(): 38 | assert Card("something", "brian", "todo", 123).to_dict() == { 39 | "summary": "something", 40 | "owner": "brian", 41 | "state": "todo", 42 | "id": 123 43 | } 44 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_02/test_classes.py: -------------------------------------------------------------------------------- 1 | from src import Card 2 | 3 | 4 | class TestEquality: 5 | def test_equality(self): 6 | assert Card("something", "brian", "todo", 123) == Card("something", "brian", "todo", 123) 7 | 8 | def test_equality_with_different_ids(self): 9 | assert Card("something", "brian", "todo", 123) == Card("something", "brian", "todo", 321) 10 | 11 | def test_inequality(self): 12 | assert Card("something", "brian", "todo", 123) != Card("completely different", "okken", "todo", 123) 13 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_02/test_exceptions.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from src import CardsDB 3 | 4 | 5 | def test_no_path_raises(): 6 | with pytest.raises(TypeError): 7 | CardsDB() 8 | 9 | 10 | def test_raises_with_info(): 11 | with pytest.raises(TypeError, match="missing 1 .* positional argument"): 12 | CardsDB() 13 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_02/test_helper.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from src import Card 4 | 5 | 6 | def assert_identical(c1: Card, c2: Card): 7 | # Do not include 'assert_identical' in traceback: 8 | __tracebackhide__ = True 9 | 10 | assert c1 == c2 11 | if c1.id != c2.id: 12 | pytest.fail(f"id's don't match. {c1.id} != {c2.id}") 13 | 14 | 15 | def test_identical(): 16 | assert_identical(Card("foo", id=123), Card("foo", id=123)) 17 | 18 | 19 | @pytest.mark.skip() 20 | def test_identical_fail(): 21 | assert_identical(Card("foo", id=123), Card("foo", id=321)) 22 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_03/conftest.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from tempfile import TemporaryDirectory 3 | 4 | import pytest 5 | 6 | from src import ( 7 | Card, 8 | CardsDB, 9 | ) 10 | 11 | 12 | @pytest.fixture(scope="session") 13 | def db(): 14 | with TemporaryDirectory() as db_dir: 15 | db_path = Path(db_dir) 16 | _db = CardsDB(db_path) 17 | yield _db 18 | _db.close() 19 | 20 | 21 | @pytest.fixture(scope="function") 22 | def cards_db(db): 23 | db.delete_all() 24 | return db 25 | 26 | 27 | @pytest.fixture(scope="session") 28 | def some_cards(): 29 | return [ 30 | Card("write book", "brian", "done"), 31 | Card("edit book", "katie", "done"), 32 | Card("write 2nd edition", "brian", "todo"), 33 | Card("edit 2nd edition", "katie", "todo"), 34 | ] 35 | 36 | 37 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_03/test_autouse.py: -------------------------------------------------------------------------------- 1 | from time import ( 2 | localtime, 3 | sleep, 4 | strftime, 5 | time, 6 | ) 7 | 8 | import pytest 9 | 10 | 11 | @pytest.fixture(scope="function") 12 | def non_empty_db(cards_db, some_cards): 13 | for c in some_cards: 14 | cards_db.add_card(c) 15 | return cards_db 16 | 17 | 18 | @pytest.fixture(autouse=True, scope="session") 19 | def footer_session_scope(): 20 | yield 21 | now = time() 22 | print("---") 23 | print(f"finished : {strftime('%d %b %X', localtime(now))}") 24 | print("--------") 25 | 26 | 27 | @pytest.fixture(autouse=True) 28 | def footer_function_scope(): 29 | start = time() 30 | yield 31 | stop = time() 32 | print(f"test duration: {stop - start:0.3}") 33 | 34 | 35 | def test_1(): 36 | sleep(1) 37 | 38 | 39 | def test_2(): 40 | sleep(1.23) 41 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_03/test_count.py: -------------------------------------------------------------------------------- 1 | from src import Card 2 | 3 | 4 | def test_empty(cards_db): 5 | assert cards_db.count() == 0 6 | 7 | 8 | def test_two(cards_db): 9 | cards_db.add_card(Card("first")) 10 | cards_db.add_card(Card("second")) 11 | assert cards_db.count() == 2 12 | 13 | 14 | def test_three(cards_db): 15 | cards_db.add_card(Card("first")) 16 | cards_db.add_card(Card("second")) 17 | cards_db.add_card(Card("three")) 18 | assert cards_db.count() == 3 19 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_03/test_count_initial.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from tempfile import TemporaryDirectory 3 | 4 | from src import CardsDB 5 | 6 | 7 | def test_empty(): 8 | with TemporaryDirectory() as db_dir: 9 | db_path = Path(db_dir) 10 | db = CardsDB(db_path) 11 | 12 | count = db.count() 13 | db.close() 14 | 15 | assert count == 0 16 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_03/test_fixtures.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture() 5 | def some_data(): 6 | return 42 7 | 8 | 9 | def test_some_data(some_data): 10 | assert some_data == 42 11 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_03/test_rename_fixture.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(name="ultimate_answer") 5 | def ultimate_answer_fixture(): 6 | return 42 7 | 8 | 9 | def test_everything(ultimate_answer): 10 | assert ultimate_answer == 42 11 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_03/test_some.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="function") 5 | def non_empty_db(cards_db, some_cards): 6 | for c in some_cards: 7 | cards_db.add_card(c) 8 | return cards_db 9 | 10 | 11 | def test_add_some(cards_db, some_cards): 12 | expected_count = len(some_cards) 13 | for c in some_cards: 14 | cards_db.add_card(c) 15 | assert cards_db.count() == expected_count 16 | 17 | 18 | def test_non_empty(non_empty_db): 19 | assert non_empty_db.count() > 0 20 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_04/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from src import CardsDB 3 | 4 | 5 | @pytest.fixture(scope="session") 6 | def db(tmp_path_factory): 7 | db_path = tmp_path_factory.mktemp("cards_db") 8 | _db = CardsDB(db_path) 9 | yield _db 10 | _db.close() 11 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_04/test_config.py: -------------------------------------------------------------------------------- 1 | import src as cards 2 | from typer.testing import CliRunner 3 | 4 | 5 | def run_cards(*params): 6 | runner = CliRunner() 7 | result = runner.invoke(cards.app, params) 8 | return result.output.rstrip() 9 | 10 | 11 | def test_run_cards(): 12 | assert run_cards("version") == cards.__version__ 13 | 14 | 15 | def test_patch_get_path(monkeypatch, tmp_path): 16 | def fake_get_path(): 17 | return tmp_path 18 | 19 | monkeypatch.setattr(cards.cli, "get_path", fake_get_path) 20 | assert run_cards("config") == str(tmp_path) 21 | 22 | 23 | def test_patch_home(monkeypatch, tmp_path): 24 | full_cards_dir = tmp_path / "cards_db" 25 | 26 | def fake_home(): 27 | return tmp_path 28 | 29 | monkeypatch.setattr(cards.cli.pathlib.Path, "home", fake_home) 30 | assert run_cards("config") == str(full_cards_dir) 31 | 32 | 33 | def test_patch_env_var(monkeypatch, tmp_path): 34 | monkeypatch.setenv("CARDS_DB_DIR", str(tmp_path)) 35 | assert run_cards("config") == str(tmp_path) 36 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_04/test_tmp.py: -------------------------------------------------------------------------------- 1 | def test_tmp_path(tmp_path): 2 | file = tmp_path / "file.txt" 3 | file.write_text("Hello") 4 | assert file.read_text() == "Hello" 5 | 6 | 7 | def test_tmp_path_factory(tmp_path_factory): 8 | path = tmp_path_factory.mktemp("sub") 9 | file = path / "file.txt" 10 | file.write_text("Hello") 11 | assert file.read_text() == "Hello" 12 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_04/test_version.py: -------------------------------------------------------------------------------- 1 | from typer.testing import CliRunner 2 | 3 | import src as cards 4 | 5 | 6 | def test_version(capsys): 7 | cards.cli.version() 8 | output = capsys.readouterr().out.rstrip() 9 | assert output == cards.__version__ 10 | 11 | 12 | def test_version_v2(): 13 | runner = CliRunner() 14 | result = runner.invoke(cards.app, ["version"]) 15 | output = result.output.rstrip() 16 | assert output == cards.__version__ 17 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_05/test_parametrize.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from src import ( 4 | Card, 5 | CardsDB, 6 | ) 7 | 8 | 9 | @pytest.fixture(scope="session") 10 | def db(tmp_path_factory): 11 | db_path = tmp_path_factory.mktemp("cards_db") 12 | _db = CardsDB(db_path) 13 | yield _db 14 | _db.close() 15 | 16 | 17 | @pytest.fixture(scope="function") 18 | def cards_db(db): 19 | db.delete_all() 20 | return db 21 | 22 | 23 | @pytest.mark.parametrize("initial_state", ["done", "in prog", "todo"]) 24 | def test_finish(cards_db, initial_state): 25 | c = Card("write a book", state=initial_state) 26 | index = cards_db.add_card(c) 27 | cards_db.finish(index) 28 | 29 | c = cards_db.get_card(index) 30 | 31 | assert c.state == "done" 32 | 33 | 34 | @pytest.fixture(params=["done", "in prog", "todo"]) 35 | def start_state(request): 36 | return request.param 37 | 38 | 39 | def test_finish_v2(cards_db, start_state): 40 | c = Card("write a book", state=start_state) 41 | index = cards_db.add_card(c) 42 | cards_db.finish(index) 43 | 44 | c = cards_db.get_card(index) 45 | 46 | assert c.state == "done" 47 | 48 | 49 | def pytest_generate_tests(metafunc): 50 | if "start_state_2" in metafunc.fixturenames: 51 | metafunc.parametrize("start_state_2", ["done", "in prog", "todo"]) 52 | 53 | 54 | def test_finish_v3(cards_db, start_state_2): 55 | c = Card("write a book", state=start_state_2) 56 | index = cards_db.add_card(c) 57 | cards_db.finish(index) 58 | 59 | c = cards_db.get_card(index) 60 | 61 | assert c.state == "done" -------------------------------------------------------------------------------- /books/pytest/tests/ch_06/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | smoke: subset of tests 4 | exception: check for expected exceptions 5 | custom: run only ch_06/custom 6 | num_cards: number of cards to prefill for cards_db fixture 7 | adopts = 8 | --stric-markers 9 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_06/test_builtin.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from tempfile import TemporaryDirectory 3 | 4 | import pytest 5 | from packaging.version import parse 6 | 7 | from src import ( 8 | Card, 9 | CardsDB, 10 | api, 11 | ) 12 | 13 | 14 | @pytest.mark.skip(reason="card doesn't support comparison yet") 15 | def test_less_than_skip(): 16 | assert Card("a task") < Card("b task") 17 | 18 | 19 | @pytest.mark.skipif( 20 | parse(api.__version__).major < 2, 21 | reason="Card comparison not supported in 1.x" 22 | ) 23 | def test_less_than_skipif(): 24 | assert Card("a task") < Card("b task") 25 | 26 | 27 | @pytest.mark.xfail( 28 | parse(api.__version__).major < 2, 29 | reason="Card comparison not supported in 1.x" 30 | ) 31 | def test_less_than_xfail(): 32 | assert Card("a task") < Card("b task") 33 | 34 | 35 | @pytest.mark.xfail(reason="XPASS demo") 36 | def test_xpass(): 37 | assert Card("a task") == Card("a task") 38 | 39 | 40 | @pytest.mark.xfail(reason="strict demo", strict=True) 41 | @pytest.mark.skip 42 | def test_xpass_strict(): 43 | assert Card("a task") == Card("a task") 44 | 45 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_06/test_custom.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from src import ( 4 | Card, 5 | CardsDB, 6 | InvalidCardId, 7 | ) 8 | 9 | pytestmark = [pytest.mark.custom] 10 | 11 | @pytest.fixture(scope="session") 12 | def db(tmp_path_factory): 13 | db_path = tmp_path_factory.mktemp("cards_db") 14 | _db = CardsDB(db_path) 15 | yield _db 16 | _db.close() 17 | 18 | 19 | @pytest.fixture(scope="function") 20 | def cards_db(db): 21 | db.delete_all() 22 | return db 23 | 24 | 25 | @pytest.mark.smoke 26 | def test_start(cards_db): 27 | i = cards_db.add_card(Card("foo", state="todo")) 28 | cards_db.start(i) 29 | c = cards_db.get_card(i) 30 | assert c.state == "in prog" 31 | 32 | 33 | @pytest.mark.exception 34 | def test_start_non_existent(cards_db): 35 | with pytest.raises(InvalidCardId): 36 | cards_db.start(123) 37 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_06/text_combination.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from src import ( 3 | Card, 4 | CardsDB, 5 | ) 6 | 7 | 8 | @pytest.fixture(scope="session") 9 | def db(tmp_path_factory): 10 | db_path = tmp_path_factory.mktemp("cards_db") 11 | _db = CardsDB(db_path) 12 | yield _db 13 | _db.close() 14 | 15 | 16 | @pytest.fixture(scope="function") 17 | def cards_db(db, request, faker): 18 | db.delete_all() 19 | 20 | faker.seed_instance(101) 21 | m = request.node.get_closest_marker("num_cards") 22 | if m and len(m.args) > 0: 23 | num_cards = m.args[0] 24 | for _ in range(num_cards): 25 | db.add_card(Card(summary=faker.sentence(), owner=faker.first_name())) 26 | return db 27 | 28 | 29 | @pytest.mark.num_cards 30 | def test_zero(cards_db): 31 | assert cards_db.count() == 0 32 | 33 | 34 | @pytest.mark.num_cards(3) 35 | def test_three(cards_db): 36 | assert cards_db.count() == 3 37 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_12/hello.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | print("Hello world") 3 | 4 | 5 | if __name__ == '__main__': 6 | main() 7 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_12/test_hello.py: -------------------------------------------------------------------------------- 1 | from tests.ch_12 import hello 2 | 3 | 4 | def test_hello(capsys): 5 | hello.main() 6 | output = capsys.readouterr().out 7 | assert output == "Hello world\n" 8 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_15/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def pytest_configure(config): 5 | config.addinivalue_line("markers", "slow: mark test as slow to run") 6 | 7 | 8 | def pytest_addoption(parser): 9 | parser.addoption("--slow", action="store_true", help="include tests marked slow") 10 | 11 | 12 | def pytest_collection_modifyitems(config, items): 13 | if not config.getoption("--slow"): 14 | skip_slow = pytest.mark.skip(reason="need --slow option to run") 15 | for item in items: 16 | if item.get_closest_marker("slow"): 17 | item.add_marker(skip_slow) 18 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_15/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = slow: mark test as slow to run 3 | -------------------------------------------------------------------------------- /books/pytest/tests/ch_15/test_slow.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_normal(): 5 | pass 6 | 7 | @pytest.mark.slow 8 | def test_slow(): 9 | pass -------------------------------------------------------------------------------- /books/python-architecture-patterns/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10.2 2 | 3 | WORKDIR /src 4 | 5 | ENV PYTHONPATH "${PYTHONPATH}:/src" 6 | 7 | COPY requirements.txt . 8 | COPY setup.cfg . 9 | 10 | RUN pip install -r requirements.txt 11 | 12 | COPY src/ src/ 13 | COPY tests/ tests/ 14 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/Makefile: -------------------------------------------------------------------------------- 1 | test-flake8: 2 | docker-compose run --rm api flake8 . 3 | 4 | test-mypy: 5 | docker-compose run --rm api mypy . 6 | 7 | test-pytest: 8 | docker-compose run --rm api pytest . 9 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | 4 | redis_pubsub: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | image: allocation-image 9 | depends_on: 10 | - postgres 11 | - redis 12 | - mailhog 13 | environment: 14 | - DB_HOST=postgres 15 | - DB_PASSWORD=abc123 16 | - REDIS_HOST=redis 17 | - EMAIL_HOST=mailhog 18 | - PYTHONDONTWRITEBYTECODE=1 19 | volumes: 20 | - ./:/src 21 | entrypoint: 22 | - python 23 | - src/redis_consumer.py 24 | 25 | api: 26 | image: allocation-image 27 | build: 28 | context: . 29 | dockerfile: Dockerfile 30 | depends_on: 31 | - redis_pubsub 32 | volumes: 33 | - ./:/src 34 | environment: 35 | - DB_HOST=postgres 36 | - DB_PASSWORD=abc123 37 | - API_HOST=api 38 | - REDIS_HOST=redis 39 | - EMAIL_HOST=mailhog 40 | - PYTHONUNBUFFERED=1 41 | - PYTHONDONTWRITEBYTECODE=1 42 | command: uvicorn src.app:api --host 0.0.0.0 --port 80 --reload 43 | ports: 44 | - "5005:80" 45 | 46 | postgres: 47 | image: postgres:14.2 48 | environment: 49 | - POSTGRES_USER=allocation 50 | - POSTGRES_PASSWORD=abc123 51 | ports: 52 | - "54321:5432" 53 | 54 | redis: 55 | image: redis:alpine 56 | ports: 57 | - "63791:6379" 58 | 59 | mailhog: 60 | image: mailhog/mailhog 61 | ports: 62 | - "11025:1025" 63 | - "18025:8025" 64 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest==6.2.5 2 | mypy==0.931 3 | flake8==4.0.1 4 | SQLAlchemy==1.4.31 5 | fastapi==0.73.0 6 | sqlmodel==0.0.6 7 | requests==2.27.1 8 | psycopg2==2.9.3 9 | uvicorn==0.17.4 10 | redis==4.1.4 11 | types-redis==4.1.17 12 | tenacity==8.0.1 13 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | python_paths = . 3 | testpaths = tests 4 | norecursedirs = .* 5 | addopts = -sl 6 | filterwarnings = 7 | ignore::DeprecationWarning 8 | ignore::PendingDeprecationWarning 9 | 10 | [mypy] 11 | python_version = 3.10 12 | ignore_missing_imports = True 13 | strict_optional = False 14 | 15 | [mypy-app.cache] 16 | ignore_errors = True 17 | 18 | [flake8] 19 | max-line-length = 180 20 | max-complexity = 10 21 | format = pylint 22 | show-source = True 23 | statistics = True 24 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/books/python-architecture-patterns/src/__init__.py -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/adapters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/books/python-architecture-patterns/src/adapters/__init__.py -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/adapters/notifications.py: -------------------------------------------------------------------------------- 1 | from abc import ( 2 | ABC, 3 | abstractmethod, 4 | ) 5 | import smtplib 6 | 7 | from src import config 8 | 9 | DEFAULT_HOST = config.get_email_host_and_port()["host"] 10 | DEFAULT_PORT = config.get_email_host_and_port()["port"] 11 | 12 | 13 | class AbstractNotifications(ABC): 14 | @abstractmethod 15 | def send(self, destination, message): 16 | raise NotImplementedError 17 | 18 | 19 | class EmailNotifications(AbstractNotifications): 20 | def __init__(self, smtp_host=DEFAULT_HOST, port=DEFAULT_PORT): 21 | self.server = smtplib.SMTP(smtp_host, port=port) 22 | self.server.noop() 23 | 24 | def send(self, destination, message): 25 | self.server.sendmail( 26 | from_addr="allocations@example.com", 27 | to_addrs=[destination], 28 | msg=f"Subject: allocation service notification\n{message}", 29 | ) 30 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/adapters/orm.py: -------------------------------------------------------------------------------- 1 | from sqlmodel import ( 2 | Field, 3 | SQLModel, 4 | ) 5 | 6 | 7 | class AllocationsView(SQLModel, table=True): 8 | id: int = Field(primary_key=True) 9 | order_id: str 10 | sku: str 11 | batch_ref: str 12 | 13 | 14 | def create_db_and_tables(engine): 15 | SQLModel.metadata.create_all(engine) 16 | 17 | 18 | def clean_db_and_tables(engine): 19 | SQLModel.metadata.drop_all(engine) 20 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/adapters/redis_publisher.py: -------------------------------------------------------------------------------- 1 | from redis.client import Redis 2 | 3 | from src import config 4 | from src.domain.events import Event 5 | 6 | r = Redis(**config.get_redis_host_and_port()) 7 | 8 | 9 | def publish(channel: str, event: Event): 10 | r.publish(channel, event.json()) 11 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/adapters/repository.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Optional, 3 | Protocol, 4 | Set, 5 | ) 6 | 7 | from sqlmodel import ( 8 | Session, 9 | select, 10 | ) 11 | 12 | from src.domain.model import ( 13 | Batch, 14 | Product, 15 | ) 16 | 17 | 18 | class AbstractRepository(Protocol): 19 | def add(self, product: Product): 20 | ... 21 | 22 | def get(self, sku: str) -> Optional[Product]: 23 | ... 24 | 25 | def get_by_batch_ref(self, ref: str) -> Optional[Product]: 26 | ... 27 | 28 | 29 | class Repository(AbstractRepository): 30 | def __init__(self, session: Session): 31 | self.session = session 32 | 33 | def add(self, product: Product): 34 | self.session.add(product) 35 | self.session.commit() 36 | 37 | def get(self, sku: str) -> Optional[Product]: 38 | return self.session.exec(select(Product).where(Product.sku == sku)).first() 39 | 40 | def get_by_batch_ref(self, ref: str) -> Optional[Product]: 41 | return self.session.exec(select(Product).join(Batch).where(Batch.reference == ref)).first() 42 | 43 | 44 | class TrackingRepository(AbstractRepository): 45 | seen: Set[Product] 46 | 47 | def __init__(self, repo: AbstractRepository): 48 | super().__init__() 49 | self.seen = set() 50 | self._repo = repo 51 | 52 | def add(self, product: Product): 53 | self._repo.add(product) 54 | self.seen.add(product) 55 | 56 | def get(self, sku: str) -> Optional[Product]: 57 | product = self._repo.get(sku) 58 | if product: 59 | self.seen.add(product) 60 | return product 61 | 62 | def get_by_batch_ref(self, ref: str) -> Optional[Product]: 63 | if product := self._repo.get_by_batch_ref(ref): 64 | self.seen.add(product) 65 | return product 66 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import ( 2 | FastAPI, 3 | Response, 4 | status, 5 | ) 6 | 7 | from src import views 8 | from src.bootstrap import bootstrap 9 | from src.domain import commands 10 | from src.domain.model import ( 11 | Batch, 12 | OrderLine, 13 | OutOfStock, 14 | ) 15 | from src.service_layer.handlers import InvalidSku 16 | 17 | bus = bootstrap() 18 | api = FastAPI() 19 | 20 | 21 | @api.post("/allocate") 22 | async def allocate_endpoint(order_line: OrderLine, response: Response): 23 | try: 24 | bus.handle(commands.Allocate(order_id=order_line.order_id, sku=order_line.sku, qty=order_line.qty)) 25 | except (OutOfStock, InvalidSku) as e: 26 | response.status_code = status.HTTP_400_BAD_REQUEST 27 | return {"message": str(e)} 28 | 29 | return {"message": "ok"} 30 | 31 | 32 | @api.post("/add_batch") 33 | async def add_batch_endpoint(batch: Batch): 34 | bus.handle(commands.CreateBatch(ref=batch.reference, sku=batch.sku, qty=batch.purchased_quantity, eta=batch.eta)) 35 | return {"message": "ok"} 36 | 37 | 38 | @api.post("/allocate/{order_id}") 39 | async def allocate_view_endpoint(order_id: str, response: Response): 40 | if result := views.allocations(order_id, bus.uow): 41 | return result 42 | response.status_code = status.HTTP_400_BAD_REQUEST 43 | return response 44 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/bootstrap.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from typing import Callable 3 | 4 | from sqlalchemy.engine import Engine 5 | from sqlmodel import create_engine 6 | 7 | from src import config 8 | from src.adapters import redis_publisher 9 | from src.adapters.notifications import ( 10 | AbstractNotifications, 11 | EmailNotifications, 12 | ) 13 | from src.adapters.orm import create_db_and_tables 14 | from src.service_layer.message_bus import ( 15 | COMMAND_HANDLERS, 16 | EVENT_HANDLERS, 17 | MessageBus, 18 | ) 19 | from src.service_layer.unit_of_work import ( 20 | AbstractUnitOfWork, 21 | UnitOfWork, 22 | ) 23 | 24 | 25 | def bootstrap(start_orm: bool = True, engine: Engine = create_engine(config.get_postgres_uri()), uow: AbstractUnitOfWork = UnitOfWork(), 26 | notifications: AbstractNotifications = EmailNotifications(), publish: Callable = redis_publisher.publish): 27 | if start_orm: 28 | create_db_and_tables(engine) 29 | 30 | dependencies = {"uow": uow, "notifications": notifications, "publish": publish} 31 | injected_event_handlers = { 32 | event_type: [ 33 | inject_dependencies(handler, dependencies) 34 | for handler in event_handlers 35 | ] 36 | for event_type, event_handlers in EVENT_HANDLERS.items() 37 | } 38 | injected_command_handlers = { 39 | command_type: inject_dependencies(handler, dependencies) 40 | for command_type, handler in COMMAND_HANDLERS.items() 41 | } 42 | 43 | return MessageBus(uow=uow, event_handlers=injected_event_handlers, command_handlers=injected_command_handlers) 44 | 45 | 46 | def inject_dependencies(handler, dependencies): 47 | params = inspect.signature(handler).parameters 48 | deps = { 49 | name: dependency 50 | for name, dependency in dependencies.items() 51 | if name in params 52 | } 53 | return lambda message: handler(message, **deps) 54 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def get_postgres_uri(): 5 | host = os.environ.get("DB_HOST", "localhost") 6 | port = 54321 if host == "localhost" else 5432 7 | password = os.environ.get("DB_PASSWORD", "abc123") 8 | user, db_name = "allocation", "allocation" 9 | return f"postgresql://{user}:{password}@{host}:{port}/{db_name}" 10 | 11 | 12 | def get_api_url(): 13 | host = os.environ.get("API_HOST", "localhost") 14 | port = 80 15 | return f"http://{host}:{port}" 16 | 17 | 18 | def get_redis_host_and_port(): 19 | host = os.environ.get("REDIS_HOST", "localhost") 20 | port = 63791 if host == "localhost" else 6379 21 | return dict(host=host, port=port) 22 | 23 | 24 | def get_email_host_and_port(): 25 | host = os.environ.get("EMAIL_HOST", "localhost") 26 | port = 11025 if host == "localhost" else 1025 27 | http_port = 18025 if host == "localhost" else 8025 28 | return dict(host=host, port=port, http_port=http_port) 29 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/books/python-architecture-patterns/src/domain/__init__.py -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/domain/commands.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import date 3 | from typing import Optional 4 | 5 | 6 | class Command: 7 | pass 8 | 9 | 10 | @dataclass 11 | class Allocate(Command): 12 | order_id: str 13 | sku: str 14 | qty: int 15 | 16 | 17 | @dataclass 18 | class CreateBatch(Command): 19 | ref: str 20 | sku: str 21 | qty: int 22 | eta: Optional[date] = None 23 | 24 | 25 | @dataclass 26 | class ChangeBatchQuantity(Command): 27 | ref: str 28 | qty: int 29 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/domain/events.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class Event(BaseModel): 5 | pass 6 | 7 | 8 | class OutOfStock(Event): 9 | sku: str 10 | 11 | 12 | class Allocated(Event): 13 | order_id: str 14 | sku: str 15 | qty: int 16 | batch_ref: str 17 | 18 | 19 | class Deallocated(Event): 20 | order_id: str 21 | sku: str 22 | qty: int 23 | 24 | 25 | class BatchQuantityChanged(Event): 26 | batch_ref: str 27 | qty: int 28 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/domain/model.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from typing import ( 3 | Iterable, 4 | List, 5 | Optional, 6 | Union, 7 | cast, 8 | ) 9 | 10 | from pydantic import PrivateAttr 11 | from pydantic.fields import ModelPrivateAttr 12 | from sqlmodel import ( 13 | Field, 14 | Relationship, 15 | SQLModel, 16 | ) 17 | 18 | from src.domain import ( 19 | commands, 20 | events, 21 | ) 22 | 23 | Message = Union[commands.Command, events.Event] 24 | 25 | 26 | class OutOfStock(Exception): 27 | pass 28 | 29 | 30 | class OrderLine(SQLModel, table=True): 31 | order_id: str 32 | sku: str 33 | qty: int 34 | # DB-specific fields: 35 | id: Optional[int] = Field(default=None, primary_key=True) 36 | batch_id: Optional[int] = Field(default=None, foreign_key="batch.id") 37 | batch: Optional["Batch"] = Relationship(back_populates="allocations") 38 | 39 | 40 | class Batch(SQLModel, table=True): 41 | reference: str 42 | sku: str 43 | purchased_quantity: int 44 | eta: Optional[date] 45 | allocations: List["OrderLine"] = Relationship(back_populates="batch") 46 | # DB-specific fields: 47 | id: Optional[int] = Field(default=None, primary_key=True) 48 | product_id: Optional[int] = Field(default=None, foreign_key="product.id") 49 | product: Optional["Product"] = Relationship(back_populates="batches") 50 | 51 | def __eq__(self, other): 52 | if not isinstance(other, Batch): 53 | return False 54 | return other.reference == self.reference 55 | 56 | def __hash__(self): 57 | return hash(self.reference) 58 | 59 | def __gt__(self, other): 60 | if self.eta is None: 61 | return False 62 | if other.eta is None: 63 | return True 64 | return self.eta > other.eta 65 | 66 | def allocate(self, order_line: OrderLine) -> None: 67 | if not self.can_allocate(order_line): 68 | return 69 | if order_line in self.allocations: 70 | return 71 | self.allocations.append(order_line) 72 | 73 | def deallocate(self, order_line: OrderLine) -> None: 74 | if order_line not in self.allocations: 75 | return 76 | self.allocations.remove(order_line) 77 | 78 | def deallocate_one(self): 79 | return self.allocations.pop() 80 | 81 | @property 82 | def allocated_quantity(self) -> int: 83 | return sum(line.qty for line in self.allocations) 84 | 85 | @property 86 | def available_quantity(self) -> int: 87 | return self.purchased_quantity - self.allocated_quantity 88 | 89 | def can_allocate(self, order_line: OrderLine) -> bool: 90 | return self.sku == order_line.sku and self.available_quantity >= order_line.qty 91 | 92 | 93 | class Product(SQLModel, table=True): 94 | sku: str 95 | batches: List["Batch"] = Relationship(back_populates="product") 96 | # DB-specific fields: 97 | id: Optional[int] = Field(default=None, primary_key=True) 98 | version_number: int = 0 99 | # DB excluded fields: 100 | _messages: ModelPrivateAttr = PrivateAttr(default=[]) 101 | 102 | def __hash__(self): 103 | return hash(self.sku) 104 | 105 | @property 106 | def messages(self) -> List[Message]: 107 | return self._messages.default 108 | 109 | def allocate(self, order_line: OrderLine) -> Optional[str]: 110 | try: 111 | batch = next(b for b in sorted(cast(Iterable, self.batches)) if b.can_allocate(order_line)) 112 | except StopIteration: 113 | self.messages.append(events.OutOfStock(sku=order_line.sku)) 114 | return None 115 | batch.allocate(order_line) 116 | self.version_number += 1 117 | self.messages.append(events.Allocated( 118 | order_id=order_line.order_id, 119 | sku=order_line.sku, 120 | qty=order_line.qty, 121 | batch_ref=batch.reference 122 | )) 123 | return batch.reference 124 | 125 | def change_batch_quantity(self, ref: str, qty: int): 126 | batch = next(b for b in self.batches if b.reference == ref) 127 | batch.purchased_quantity = qty 128 | while batch.available_quantity < 0: 129 | line = batch.deallocate_one() 130 | self.messages.append(commands.Allocate(order_id=line.order_id, sku=line.sku, qty=line.qty)) 131 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/redis_consumer.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Dict 3 | 4 | from redis.client import Redis 5 | 6 | from src import config 7 | from src.bootstrap import bootstrap 8 | from src.domain import ( 9 | commands, 10 | events, 11 | ) 12 | from src.service_layer.message_bus import MessageBus 13 | 14 | r = Redis(**config.get_redis_host_and_port()) 15 | 16 | 17 | def main(): 18 | bus = bootstrap() 19 | pubsub = r.pubsub(ignore_subscribe_messages=True) 20 | pubsub.subscribe("change_batch_quantity") 21 | 22 | for m in pubsub.listen(): 23 | _handle_change_batch_quantity(m, bus) 24 | 25 | 26 | def _handle_change_batch_quantity(message: Dict, bus: MessageBus): 27 | event = events.BatchQuantityChanged(**json.loads(message["data"])) 28 | cmd = commands.ChangeBatchQuantity(ref=event.batch_ref, qty=event.qty) 29 | 30 | bus.handle(message=cmd) 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/service_layer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/books/python-architecture-patterns/src/service_layer/__init__.py -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/service_layer/handlers.py: -------------------------------------------------------------------------------- 1 | from src.adapters import redis_publisher 2 | from src.adapters.notifications import AbstractNotifications 3 | from src.domain import ( 4 | commands, 5 | events, 6 | ) 7 | 8 | from src.domain.model import ( 9 | Batch, 10 | OrderLine, 11 | Product, 12 | ) 13 | from src.service_layer.unit_of_work import ( 14 | AbstractUnitOfWork, 15 | UnitOfWork, 16 | ) 17 | 18 | 19 | class InvalidSku(Exception): 20 | pass 21 | 22 | 23 | def allocate(command: commands.Allocate, uow: AbstractUnitOfWork) -> str: 24 | order_line = OrderLine(order_id=command.order_id, sku=command.sku, qty=command.qty) 25 | with uow: 26 | product = uow.products.get(sku=command.sku) 27 | 28 | if not product: 29 | raise InvalidSku(f"Invalid SKU: {command.sku}") 30 | 31 | batch_ref = product.allocate(order_line) 32 | uow.commit() 33 | 34 | return batch_ref 35 | 36 | 37 | def add_batch(command: commands.CreateBatch, uow: AbstractUnitOfWork): 38 | with uow: 39 | product = uow.products.get(command.sku) 40 | if not product: 41 | product = Product(sku=command.sku, batches=[]) 42 | uow.products.add(product) 43 | product.batches.append(Batch(reference=command.ref, sku=command.sku, purchased_quantity=command.qty, eta=command.eta)) 44 | uow.commit() 45 | 46 | 47 | def change_batch_quantity(command: commands.ChangeBatchQuantity, uow: AbstractUnitOfWork): 48 | with uow: 49 | product = uow.products.get_by_batch_ref(command.ref) 50 | product.change_batch_quantity(ref=command.ref, qty=command.qty) 51 | uow.commit() 52 | 53 | 54 | def send_out_of_stock_notification(event: events.OutOfStock, notifications: AbstractNotifications): 55 | notifications.send("stock@made.com", f"Out of stock for {event.sku}") 56 | 57 | 58 | def publish_allocated_event(event: events.Allocated, uow: AbstractUnitOfWork): 59 | redis_publisher.publish("line_allocated", event) 60 | 61 | 62 | def add_allocation_to_read_model(event: events.Allocated, uow: UnitOfWork): 63 | with uow: 64 | uow.session.execute( 65 | """ 66 | INSERT INTO allocationsview (order_id, sku, batch_ref) 67 | VALUES (:order_id, :sku, :batch_ref) 68 | """, 69 | dict(order_id=event.order_id, sku=event.sku, batch_ref=event.batch_ref), 70 | ) 71 | uow.commit() 72 | 73 | 74 | def remove_allocation_from_read_model(event: events.Deallocated, uow: UnitOfWork): 75 | with uow: 76 | uow.session.execute( 77 | """ 78 | DELETE FROM allocationsview 79 | WHERE order_id = :order_id AND sku = :sku 80 | """, 81 | dict(order_id=event.order_id, sku=event.sku), 82 | ) 83 | uow.commit() 84 | 85 | 86 | def reallocate(event: events.Deallocated, uow: AbstractUnitOfWork, ): 87 | with uow: 88 | product = uow.products.get(sku=event.sku) 89 | product.messages.append(commands.Allocate(**event.dict())) 90 | uow.commit() 91 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/service_layer/message_bus.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import ( 3 | Callable, 4 | Dict, 5 | List, 6 | Type, 7 | Union, 8 | ) 9 | 10 | from src.domain import ( 11 | commands, 12 | events, 13 | ) 14 | from src.service_layer import handlers 15 | from src.service_layer.unit_of_work import AbstractUnitOfWork 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | Message = Union[commands.Command, events.Event] 20 | 21 | EVENT_HANDLERS: Dict[Type[events.Event], List[Callable]] = { 22 | events.OutOfStock: [handlers.send_out_of_stock_notification], 23 | events.Allocated: [handlers.publish_allocated_event, handlers.add_allocation_to_read_model], 24 | events.Deallocated: [handlers.remove_allocation_from_read_model, handlers.reallocate] 25 | } 26 | 27 | COMMAND_HANDLERS: Dict[Type[commands.Command], Callable] = { 28 | commands.CreateBatch: handlers.add_batch, 29 | commands.ChangeBatchQuantity: handlers.change_batch_quantity, 30 | commands.Allocate: handlers.allocate, 31 | } 32 | 33 | 34 | class MessageBus: 35 | def __init__(self, uow: AbstractUnitOfWork, event_handlers: Dict[Type[events.Event], List[Callable]], command_handlers: Dict[Type[commands.Command], Callable]): 36 | self.uow = uow 37 | self.event_handlers = event_handlers 38 | self.command_handlers = command_handlers 39 | 40 | self.queue: List[Message] = [] 41 | 42 | def handle(self, message: Message): 43 | self.queue = [message] 44 | while self.queue: 45 | message = self.queue.pop(0) 46 | if isinstance(message, events.Event): 47 | self._handle_event(message) 48 | elif isinstance(message, commands.Command): 49 | self._handle_command(message) 50 | else: 51 | raise Exception(f"{message} was not an Event or Command") 52 | 53 | def _handle_event(self, event: events.Event): 54 | for handler in self.event_handlers[type(event)]: 55 | try: 56 | logger.debug(f"Handling event {event} with handler {handler}") 57 | handler(event) 58 | self.queue.extend(self.uow.collect_new_messages()) 59 | except Exception as e: 60 | logger.exception(f"Exception handling event {event}: {e}") 61 | continue 62 | 63 | def _handle_command(self, command: commands.Command): 64 | try: 65 | handler = self.command_handlers[type(command)] 66 | handler(command) 67 | self.queue.extend(self.uow.collect_new_messages()) 68 | except Exception: 69 | logger.exception("Exception handling command %s", command) 70 | raise 71 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/service_layer/unit_of_work.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ( 4 | ABC, 5 | abstractmethod, 6 | ) 7 | from typing import Optional 8 | 9 | from sqlmodel import ( 10 | Session, 11 | create_engine, 12 | ) 13 | 14 | from src.adapters.repository import ( 15 | Repository, 16 | TrackingRepository, 17 | ) 18 | from src.config import get_postgres_uri 19 | 20 | 21 | class AbstractUnitOfWork(ABC): 22 | products: TrackingRepository 23 | 24 | def __enter__(self) -> AbstractUnitOfWork: 25 | return self 26 | 27 | def __exit__(self, *args): 28 | self.rollback() 29 | 30 | def commit(self): 31 | self._commit() 32 | 33 | def collect_new_messages(self): 34 | for product in self.products.seen: 35 | while product.messages: 36 | yield product.messages.pop(0) 37 | 38 | @abstractmethod 39 | def rollback(self): 40 | raise NotImplementedError 41 | 42 | @abstractmethod 43 | def _commit(self): 44 | raise NotImplementedError 45 | 46 | 47 | def default_session(): 48 | return Session(create_engine(get_postgres_uri(), isolation_level="REPEATABLE READ")) 49 | 50 | 51 | class UnitOfWork(AbstractUnitOfWork): 52 | def __init__(self, session: Optional[Session] = None): 53 | # 'default_session()' can not be in the '__init__' because it would be evaluated only once: 54 | self.session = session if session else default_session() 55 | 56 | def __enter__(self): 57 | self.products = TrackingRepository(repo=Repository(self.session)) 58 | return super().__enter__() 59 | 60 | def __exit__(self, *args): 61 | super().__exit__(*args) 62 | self.session.close() 63 | 64 | def rollback(self): 65 | self.session.rollback() 66 | 67 | def _commit(self): 68 | self.session.commit() 69 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/src/views.py: -------------------------------------------------------------------------------- 1 | from typing import ( 2 | Dict, 3 | List, 4 | ) 5 | 6 | from src.service_layer.unit_of_work import UnitOfWork 7 | 8 | 9 | def allocations(order_id: str, uow: UnitOfWork) -> List[Dict]: 10 | with uow: 11 | results = uow.session.execute( 12 | "SELECT sku, batch_ref FROM allocationsview WHERE order_id = :order_id", 13 | dict(order_id=order_id), 14 | ) 15 | return [dict(r) for r in results] 16 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/books/python-architecture-patterns/tests/__init__.py -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import redis 3 | from sqlmodel import ( 4 | Session, 5 | create_engine, 6 | ) 7 | from starlette.testclient import TestClient 8 | from tenacity import ( 9 | retry, 10 | stop_after_delay, 11 | ) 12 | 13 | from src import config 14 | from src.adapters.orm import ( 15 | clean_db_and_tables, 16 | create_db_and_tables, 17 | ) 18 | from src.app import api 19 | 20 | 21 | @pytest.fixture 22 | def in_memory_db(): 23 | engine = create_engine("sqlite:///:memory:") 24 | clean_db_and_tables(engine) 25 | create_db_and_tables(engine) 26 | return engine 27 | 28 | 29 | @pytest.fixture 30 | def session(in_memory_db): 31 | create_db_and_tables(in_memory_db) 32 | yield Session(in_memory_db) 33 | clean_db_and_tables(in_memory_db) 34 | 35 | 36 | @retry(stop=stop_after_delay(10)) 37 | def wait_for_postgres_to_come_up(engine): 38 | engine.connect() 39 | 40 | 41 | @retry(stop=stop_after_delay(10)) 42 | def wait_for_redis_to_come_up(): 43 | r = redis.Redis(**config.get_redis_host_and_port()) 44 | return r.ping() 45 | 46 | 47 | @pytest.fixture(scope="session") 48 | def postgres_db(): 49 | engine = create_engine(config.get_postgres_uri()) 50 | wait_for_postgres_to_come_up(engine) 51 | clean_db_and_tables(engine) 52 | create_db_and_tables(engine) 53 | return engine 54 | 55 | 56 | @pytest.fixture 57 | def postgres_session(postgres_db): 58 | create_db_and_tables(postgres_db) 59 | yield Session(postgres_db) 60 | clean_db_and_tables(postgres_db) 61 | 62 | 63 | @pytest.fixture 64 | def client(): 65 | return TestClient(api) 66 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/e2e/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/books/python-architecture-patterns/tests/e2e/__init__.py -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/e2e/api_client.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from src.domain.model import ( 4 | Batch, 5 | OrderLine, 6 | ) 7 | 8 | 9 | def post_to_allocate(client, order_id, sku, qty): 10 | return client.post("/allocate", json=json.loads(OrderLine(order_id=order_id, sku=sku, qty=qty).json())) 11 | 12 | 13 | def get_allocation(client, order_id): 14 | return client.post(f"/allocate/{order_id}") 15 | 16 | 17 | def post_to_add_batch(client, ref, sku, qty, eta): 18 | return client.post("/add_batch", json=json.loads(Batch(reference=ref, sku=sku, purchased_quantity=qty, eta=eta).json())) 19 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/e2e/redis_client.py: -------------------------------------------------------------------------------- 1 | import json 2 | import redis 3 | 4 | from src import config 5 | 6 | r = redis.Redis(**config.get_redis_host_and_port()) 7 | 8 | 9 | def subscribe_to(channel): 10 | pubsub = r.pubsub() 11 | pubsub.subscribe(channel) 12 | confirmation = pubsub.get_message(timeout=3) 13 | assert confirmation["type"] == "subscribe" 14 | return pubsub 15 | 16 | 17 | def publish_message(channel, message): 18 | r.publish(channel, json.dumps(message)) 19 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/e2e/test_app.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from uuid import uuid4 3 | 4 | from tests.e2e.api_client import ( 5 | get_allocation, 6 | post_to_add_batch, 7 | post_to_allocate, 8 | ) 9 | 10 | 11 | def random_suffix(): 12 | return uuid4().hex[:6] 13 | 14 | 15 | def random_sku(name=''): 16 | return f"sku-{name}-{random_suffix()}" 17 | 18 | 19 | def random_batch_ref(name=''): 20 | return f"batch-{name}-{random_suffix()}" 21 | 22 | 23 | def random_order_id(name=''): 24 | return f"order-{name}-{random_suffix()}" 25 | 26 | 27 | def test_happy_path_returns_200_and_allocated_batch(client): 28 | sku, other_sku = random_sku(), random_sku("other") 29 | order_id = random_order_id() 30 | early_batch, later_batch, other_batch = random_batch_ref('1'), random_batch_ref('2'), random_batch_ref('3') 31 | post_to_add_batch(client, later_batch, sku, 100, date(2011, 1, 2)) 32 | post_to_add_batch(client, early_batch, sku, 100, date(2011, 1, 1)) 33 | post_to_add_batch(client, other_batch, other_sku, 100, None) 34 | 35 | response = post_to_allocate(client=client, order_id=order_id, sku=sku, qty=3) 36 | assert response.status_code == 200, response.status_code 37 | 38 | response = get_allocation(client=client, order_id=order_id) 39 | assert response.status_code == 200 40 | assert response.json() == [{"sku": sku, "batch_ref": early_batch}] 41 | 42 | 43 | def test_unhappy_path_returns_400_and_error_message(client): 44 | unknown_order_id, unknown_sku = random_order_id(), random_sku() 45 | response = post_to_allocate(client=client, order_id=random_order_id(), sku=unknown_sku, qty=20) 46 | 47 | assert response.status_code == 400 48 | assert response.json()["message"] == f"Invalid SKU: {unknown_sku}" 49 | 50 | response = get_allocation(client=client, order_id=unknown_order_id) 51 | assert response.status_code == 400 52 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/e2e/test_external_events.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import date 3 | 4 | import pytest 5 | from tenacity import ( 6 | Retrying, 7 | stop_after_delay, 8 | ) 9 | 10 | from tests.e2e import redis_client 11 | from tests.e2e.api_client import ( 12 | post_to_add_batch, 13 | post_to_allocate, 14 | ) 15 | from tests.e2e.test_app import ( 16 | random_batch_ref, 17 | random_order_id, 18 | random_sku, 19 | ) 20 | 21 | 22 | def test_change_batch_quantity_leading_to_allocation(client): 23 | order_id, sku = random_order_id(), random_sku() 24 | earlier_batch, later_batch = random_batch_ref("old"), random_batch_ref("new") 25 | post_to_add_batch(client=client, ref=earlier_batch, sku=sku, qty=10, eta=date(2021, 1, 1)) 26 | post_to_add_batch(client=client, ref=later_batch, sku=sku, qty=10, eta=date(2021, 1, 2)) 27 | 28 | response = post_to_allocate(client=client, order_id=order_id, sku=sku, qty=10) 29 | assert response.status_code == 200 30 | 31 | subscription = redis_client.subscribe_to("line_allocated") 32 | 33 | redis_client.publish_message("change_batch_quantity", {"batch_ref": earlier_batch, "qty": 5}) 34 | 35 | # it may take some for message to arrive: 36 | for attempt in Retrying(stop=stop_after_delay(3), reraise=True): 37 | with attempt: 38 | message = subscription.get_message(timeout=1) 39 | if not message: 40 | continue 41 | data = json.loads(message["data"]) 42 | assert data["order_id"] == order_id 43 | assert data["batch_ref"] == later_batch 44 | if not message: 45 | pytest.fail("Message not fetched") 46 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/books/python-architecture-patterns/tests/integration/__init__.py -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/integration/test_uow.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | from time import sleep 3 | from typing import List 4 | 5 | import pytest 6 | from sqlalchemy.orm import selectinload 7 | from sqlmodel import ( 8 | Session, 9 | select, 10 | ) 11 | 12 | from src.domain.model import ( 13 | Batch, 14 | OrderLine, 15 | Product, 16 | ) 17 | from src.service_layer.unit_of_work import UnitOfWork 18 | from tests.e2e.test_app import random_batch_ref 19 | 20 | sku = "GENERIC-SOFA" 21 | 22 | 23 | def insert_batch(session, batch_id): 24 | session.add(Product(sku=sku, batches=[Batch(reference=batch_id, sku=sku, purchased_quantity=100, eta=None)])) 25 | 26 | 27 | def get_allocated_batch_ref(session, order_id, sku): 28 | batches = session.exec(select(Batch).where(Batch.sku == sku).options(selectinload(Batch.allocations))).all() 29 | batch = next(batch for batch in batches for allocation in batch.allocations if allocation.order_id == order_id) 30 | return batch.reference 31 | 32 | 33 | def test_uow_retrieve_batch_and_allocate_to_it(session): 34 | insert_batch(session, "batch1") 35 | session.commit() 36 | 37 | with UnitOfWork(session) as uow: 38 | product = uow.products.get(sku=sku) 39 | line = OrderLine(order_id="o1", sku=sku, qty=10) 40 | product.allocate(order_line=line) 41 | uow.commit() 42 | 43 | assert get_allocated_batch_ref(session, "o1", "GENERIC-SOFA") == "batch1" 44 | 45 | 46 | def test_rolls_back_uncommitted_work_by_default(in_memory_db): 47 | old_session, new_session = Session(in_memory_db), Session(in_memory_db) 48 | with UnitOfWork(): 49 | insert_batch(old_session, "batch1") 50 | assert list(new_session.exec(select(Batch)).all()) == [] 51 | 52 | 53 | def test_rolls_back_on_error(in_memory_db): 54 | old_session, new_session = Session(in_memory_db), Session(in_memory_db) 55 | 56 | class MyException(Exception): 57 | pass 58 | 59 | with pytest.raises(MyException): 60 | with UnitOfWork(old_session): 61 | insert_batch(old_session, "batch1") 62 | raise MyException() 63 | 64 | assert list(new_session.exec(select(Batch)).all()) == [] 65 | 66 | 67 | def try_to_allocate(order_id: str, exceptions: List[Exception]): 68 | line = OrderLine(order_id=order_id, sku=sku, qty=10) 69 | try: 70 | with UnitOfWork() as uow: 71 | product = uow.products.get(sku) 72 | product.allocate(line) 73 | sleep(0.2) 74 | uow.commit() 75 | except Exception as e: 76 | exceptions.append(e) 77 | 78 | 79 | def test_concurrent_updates_to_version_number_are_not_allowed(postgres_db): 80 | session = Session(postgres_db) 81 | insert_batch(session, random_batch_ref()) 82 | session.commit() 83 | exceptions = [] 84 | 85 | t1, t2 = Thread(target=try_to_allocate, args=("order_id_1", exceptions)), Thread(target=try_to_allocate, args=("order_id_2", exceptions)) 86 | t1.start(), t2.start(), t1.join(), t2.join() 87 | 88 | product = session.exec(select(Product).where(Product.sku == sku)).one() 89 | assert product.version_number == 1 90 | assert "could not serialize access due to concurrent update" in str(exceptions[0]) 91 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/integration/test_views.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from unittest.mock import Mock 3 | 4 | import pytest 5 | from sqlmodel import Session 6 | 7 | from src import views 8 | from src.adapters.orm import clean_db_and_tables 9 | from src.bootstrap import bootstrap 10 | from src.domain import commands 11 | from src.service_layer.unit_of_work import UnitOfWork 12 | 13 | today = date.today() 14 | 15 | 16 | @pytest.fixture 17 | def sqlite_bus(in_memory_db): 18 | bus = bootstrap( 19 | start_orm=True, 20 | uow=UnitOfWork(Session(in_memory_db)), 21 | notifications=Mock(), 22 | publish=lambda *args: None, 23 | ) 24 | yield bus 25 | clean_db_and_tables(in_memory_db) 26 | 27 | 28 | def test_allocations_view(sqlite_bus): 29 | sqlite_bus.handle(commands.CreateBatch("sku1batch", "sku1", 50, None)) 30 | sqlite_bus.handle(commands.CreateBatch("sku2batch", "sku2", 50, today)) 31 | sqlite_bus.handle(commands.Allocate("order1", "sku1", 20)) 32 | sqlite_bus.handle(commands.Allocate("order1", "sku2", 20)) 33 | 34 | sqlite_bus.handle(commands.CreateBatch("sku1batch-later", "sku1", 50, today)) 35 | sqlite_bus.handle(commands.Allocate("other_order", "sku1", 30)) 36 | sqlite_bus.handle(commands.Allocate("other_order", "sku2", 10)) 37 | 38 | assert views.allocations("order1", sqlite_bus.uow) == [ 39 | {"sku": "sku1", "batch_ref": "sku1batch"}, 40 | {"sku": "sku2", "batch_ref": "sku2batch"}, 41 | ] 42 | 43 | 44 | def test_deallocation(sqlite_bus): 45 | sqlite_bus.handle(commands.CreateBatch("b1", "sku1", 50, None)) 46 | sqlite_bus.handle(commands.CreateBatch("b2", "sku1", 50, today)) 47 | sqlite_bus.handle(commands.Allocate("o1", "sku1", 40)) 48 | sqlite_bus.handle(commands.ChangeBatchQuantity("b1", 10)) 49 | 50 | assert views.allocations("o1", sqlite_bus.uow) == [ 51 | {"batch_ref": "b1", "sku": "sku1"}, 52 | {"batch_ref": "b2", "sku": "sku1"} 53 | ] 54 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/books/python-architecture-patterns/tests/unit/__init__.py -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/unit/test_batches.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | 3 | from src.domain.model import ( 4 | Batch, 5 | OrderLine, 6 | ) 7 | 8 | 9 | def batch_and_line(sku, batch_quantity, line_quantity): 10 | return Batch(reference="batch-001", sku=sku, purchased_quantity=batch_quantity, eta=date.today()), OrderLine(order_id="order-123", sku=sku, qty=line_quantity) 11 | 12 | 13 | def test_allocating_to_batch_reduces_available_quantity(): 14 | batch, line = batch_and_line("SMALL-TABLE", 20, 2) 15 | batch.allocate(line) 16 | assert batch.available_quantity == 18 17 | 18 | 19 | def test_can_allocate_if_available_greater_than_required(): 20 | large_batch, small_line = batch_and_line("ELEGANT-LAMP", 20, 2) 21 | assert large_batch.can_allocate(small_line) 22 | 23 | 24 | def test_cannot_allocate_if_available_smaller_than_required(): 25 | small_batch, large_line = batch_and_line("ELEGANT-LAMP", 2, 20) 26 | assert not small_batch.can_allocate(large_line) 27 | 28 | 29 | def test_not_allocate_if_available_equal_to_required(): 30 | small_batch, large_line = batch_and_line("ELEGANT-LAMP", 2, 2) 31 | assert small_batch.can_allocate(large_line) 32 | 33 | 34 | def test_cannot_allocate_if_skus_dont_match(): 35 | batch = Batch(reference="batch-001", sku="UNCOMFORTABLE-CHAIN", purchased_quantity=100, eta=None) 36 | different_sku_line = OrderLine(order_id="order-123", sku="EXPENSIVE-TOASTER", qty=10) 37 | assert not batch.can_allocate(different_sku_line) 38 | 39 | 40 | def test_can_only_deallocate_allocated_lines(): 41 | batch, unallocated_line = batch_and_line("DECORATIVE-TRINKET", 20, 2) 42 | batch.deallocate(unallocated_line) 43 | assert batch.available_quantity == 20 44 | 45 | 46 | def test_allocation_is_idempotent(): 47 | batch, line = batch_and_line("ANGULAR-DESK", 20, 2) 48 | batch.allocate(line) 49 | batch.allocate(line) 50 | assert batch.available_quantity == 18 51 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/unit/test_handlers.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from collections import defaultdict 4 | from datetime import date 5 | from typing import ( 6 | Dict, 7 | List, 8 | Optional, 9 | ) 10 | 11 | import pytest 12 | 13 | from src.adapters.notifications import AbstractNotifications 14 | from src.adapters.repository import ( 15 | AbstractRepository, 16 | TrackingRepository, 17 | ) 18 | from src.bootstrap import bootstrap 19 | from src.domain import commands 20 | from src.domain.model import Product 21 | from src.service_layer.handlers import InvalidSku 22 | from src.service_layer.unit_of_work import AbstractUnitOfWork 23 | 24 | 25 | class FakeRepository(AbstractRepository): 26 | def __init__(self, products): 27 | super().__init__() 28 | self._products = set(products) 29 | 30 | def add(self, product: Product): 31 | self._products.add(product) 32 | 33 | def get(self, sku: str) -> Optional[Product]: 34 | return next((product for product in self._products if product.sku == sku), None) 35 | 36 | def get_by_batch_ref(self, ref: str) -> Optional[Product]: 37 | return next((product for product in self._products for batch in product.batches if batch.reference == ref), None) 38 | 39 | 40 | class FakeUnitOfWork(AbstractUnitOfWork): 41 | def __init__(self): 42 | self.products = TrackingRepository(repo=FakeRepository([])) 43 | self.committed = False 44 | 45 | def rollback(self): 46 | pass 47 | 48 | def _commit(self): 49 | self.committed = True 50 | 51 | 52 | class FakeNotifications(AbstractNotifications): 53 | def __init__(self): 54 | self.sent: Dict[str, List[str]] = defaultdict(list) 55 | 56 | def send(self, destination, message): 57 | self.sent[destination].append(message) 58 | 59 | 60 | def bootstrap_test_app(): 61 | return bootstrap( 62 | start_orm=False, 63 | uow=FakeUnitOfWork(), 64 | notifications=FakeNotifications(), 65 | publish=lambda *args: None, 66 | ) 67 | 68 | 69 | class TestAddBatch: 70 | def test_for_new_product(self): 71 | bus = bootstrap_test_app() 72 | bus.handle(commands.CreateBatch(ref="b1", sku="CRUNCHY-ARMCHAIN", qty=100)) 73 | assert bus.uow.products.get("CRUNCHY-ARMCHAIN") is not None 74 | assert bus.uow.committed 75 | 76 | def test_for_existing_product(self): 77 | bus = bootstrap_test_app() 78 | bus.handle(commands.CreateBatch(ref="b1", sku="GARISH-RUG", qty=100)) 79 | bus.handle(commands.CreateBatch(ref="b2", sku="GARISH-RUG", qty=99)) 80 | assert "b2" in [b.reference for b in bus.uow.products.get("GARISH-RUG").batches] 81 | 82 | 83 | class TestAllocate: 84 | def test_errors_for_invalid_sku(self): 85 | bus = bootstrap_test_app() 86 | bus.handle(commands.CreateBatch(ref="b1", sku="AREALSKU", qty=100)) 87 | with pytest.raises(InvalidSku, match="Invalid SKU: NONEXISTENTSKU"): 88 | bus.handle(commands.Allocate(order_id="o1", sku="NONEXISTENTSKU", qty=10)) 89 | 90 | def test_commits(self): 91 | bus = bootstrap_test_app() 92 | bus.handle(commands.CreateBatch(ref="b1", sku="OMINOUS-MIRROR", qty=100)) 93 | bus.handle(commands.Allocate(order_id="o1", sku="OMINOUS-MIRROR", qty=10)) 94 | assert bus.uow.committed 95 | 96 | def test_sends_email_on_out_of_stock_error(self): 97 | fake_notifications = FakeNotifications() 98 | bus = bootstrap( 99 | start_orm=False, 100 | uow=FakeUnitOfWork(), 101 | notifications=fake_notifications, 102 | publish=lambda *args: None, 103 | ) 104 | bus.handle(commands.CreateBatch(ref="b1", sku="POPULAR-CURTAINS", qty=9)) 105 | bus.handle(commands.Allocate(order_id="o1", sku="POPULAR-CURTAINS", qty=10)) 106 | assert fake_notifications.sent["stock@made.com"] == ["Out of stock for POPULAR-CURTAINS"] 107 | 108 | 109 | class TestChangeBatchQuantity: 110 | def test_changes_available_quantity(self): 111 | bus = bootstrap_test_app() 112 | bus.handle(commands.CreateBatch(ref="batch1", sku="ADORABLE-SETTEE", qty=100)) 113 | 114 | [batch] = bus.uow.products.get("ADORABLE-SETTEE").batches 115 | assert batch.available_quantity == 100 116 | 117 | bus.handle(commands.ChangeBatchQuantity(ref="batch1", qty=50)) 118 | assert batch.available_quantity == 50 119 | 120 | def test_reallocates_if_necessary(self): 121 | bus = bootstrap_test_app() 122 | event_history = [ 123 | commands.CreateBatch(ref="batch1", sku="INDIFFERENT-TABLE", qty=50), 124 | commands.CreateBatch(ref="batch2", sku="INDIFFERENT-TABLE", qty=50, eta=date.today()), 125 | commands.Allocate(order_id="order1", sku="INDIFFERENT-TABLE", qty=20), 126 | commands.Allocate(order_id="order2", sku="INDIFFERENT-TABLE", qty=20), 127 | ] 128 | for e in event_history: 129 | bus.handle(e) 130 | 131 | [batch_1, batch_2] = bus.uow.products.get("INDIFFERENT-TABLE").batches 132 | assert batch_1.available_quantity == 10 133 | assert batch_2.available_quantity == 50 134 | 135 | bus.handle(commands.ChangeBatchQuantity(ref="batch1", qty=25)) 136 | assert batch_1.available_quantity == 5 137 | assert batch_2.available_quantity == 30 138 | -------------------------------------------------------------------------------- /books/python-architecture-patterns/tests/unit/test_product.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | 3 | from src.domain import events 4 | from src.domain.model import ( 5 | Batch, 6 | OrderLine, 7 | Product, 8 | ) 9 | 10 | 11 | def test_prefers_current_stock_batches_to_shipments(): 12 | in_stock_batch = Batch(reference="in-stock-batch", sku="RETRO-CLOCK", purchased_quantity=100, eta=None) 13 | shipment_batch = Batch(reference="shipment-batch", sku="RETRO-CLOCK", purchased_quantity=100, eta=None) 14 | line = OrderLine(order_id="oref", sku="RETRO-CLOCK", qty=10) 15 | product = Product(sku="RETRO-CLOCK", batches=[in_stock_batch, shipment_batch]) 16 | 17 | product.allocate(line) 18 | 19 | assert in_stock_batch.available_quantity == 90 20 | assert shipment_batch.available_quantity == 100 21 | 22 | 23 | def test_prefers_earlier_batches(): 24 | earliest = Batch(reference="speedy-batch", sku="MINIMALIST-SPOON", purchased_quantity=100, eta=date(2022, 1, 7)) 25 | medium = Batch(reference="normal-batch", sku="MINIMALIST-SPOON", purchased_quantity=100, eta=date(2022, 1, 8)) 26 | latest = Batch(reference="slow-batch", sku="MINIMALIST-SPOON", purchased_quantity=100, eta=date(2022, 1, 9)) 27 | line = OrderLine(order_id="oref", sku="MINIMALIST-SPOON", qty=10) 28 | product = Product(sku="MINIMALIST-SPOON", batches=[medium, earliest, latest]) 29 | 30 | product.allocate(line) 31 | 32 | assert earliest.available_quantity == 90 33 | assert medium.available_quantity == 100 34 | assert latest.available_quantity == 100 35 | 36 | 37 | def test_returns_allocated_batch_ref(): 38 | in_stock_batch = Batch(reference="in-stock-batch-ref", sku="HIGHBROW-POSTER", purchased_quantity=100, eta=None) 39 | shipment_batch = Batch(reference="shipment-batch-ref", sku="HIGHBROW-POSTER", purchased_quantity=100, eta=date(2022, 1, 7)) 40 | line = OrderLine(order_id="oref", sku="HIGHBROW-POSTER", qty=10) 41 | product = Product(sku="HIGHBROW-POSTER", batches=[in_stock_batch, shipment_batch]) 42 | 43 | allocation = product.allocate(line) 44 | 45 | assert allocation == in_stock_batch.reference 46 | 47 | 48 | def test_records_out_of_stock_event_if_cannot_allocate(): 49 | batch = Batch(reference="batch", sku="SMALL-FORM", purchased_quantity=10, eta=date(2022, 1, 7)) 50 | product = Product(sku="SMALL-FORK", batches=[batch]) 51 | product.allocate(OrderLine(order_id="oref", sku="SMALL-FORM", qty=10)) 52 | 53 | allocation = product.allocate(OrderLine(order_id="oref", sku="SMALL-FORM", qty=1)) 54 | 55 | assert product.messages[-1] == events.OutOfStock(sku="SMALL-FORM") 56 | assert allocation is None 57 | -------------------------------------------------------------------------------- /books/system-design-interview.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # System Design Interview 4 | 5 | Book by Alex Xu & Sahn Lam 6 | 7 | - [1. Proximity Service](#1-proximity-service) 8 | 9 | ## 1. Proximity Service 10 | -------------------------------------------------------------------------------- /books/understanding-distributed-systems.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Understanding Distributed Systems: What every developer should know about large distributed applications 4 | 5 | Book by Roberto Vitillo 6 | -------------------------------------------------------------------------------- /case-studies/reddit.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # How Reddit mastered managing growth 4 | 5 | *Presentation by Greg Taylor* 6 | 7 | 330M monthly active users. 8th most popular website in the World. 12M posts per month. 2B votes per month. 8 | 9 | Reddit in 2016 - small engineering team with a monolith application. The Infrastructure team was responsible for 10 | provisioning and configuring all infrastructure, operating most of the systems and handling non-trivial debugging. 11 | Static infrastructure. This approach worked for more than a decade. 12 | 13 | In 2016 team started rapidly growing. But monolith application was so fragile, every deploy was an adventure - blocker 14 | for the organisation. 15 | 16 | How to make everyone's life easier? How to onboard new employees? 17 | 18 | Reddit decided to pursue with SOA - Service-Oriented-Architecture. This gave better separation of concerns between 19 | teams. However, if you have a monolith, and it works well for you: "go home, give it a hug, tell it you love it, warts 20 | and all". 21 | 22 | Growing pains: Automated tests - they started using CI, master branch always had to be green. 23 | 24 | Growing pains: Something to build on - instead of copying and pasting services out from another they needed to have a 25 | service framework to base off of. Services are configured in the same way, they expose similar set of ports, they have 26 | the same async event loop, they fetch secrets the same way, ... - baseplate.readthedocs.io 27 | 28 | Growing pains: Artisanal infrastructure - they had hand-crated infrastructure, switched to Terraform (infrastructure as 29 | code) - reusable modules - really valuable. Pulling existing infrastructure to Terraform was painful. 30 | 31 | Growing pains: Staging/integration woes - their approach for staging was inappropriate for SOA, so they started using 32 | Kubernetes. 33 | 34 | Growing pains: Infra team as a bottleneck - everything was depending on the infrastructure team, so they gave developers 35 | more freedom to modify Terraform. Not all teams want to operate the full stack for their service. 36 | 37 | Service ownership, service owner is empowered to: 38 | 39 | - Dev and test their service in a prod-like env 40 | - Do most of the work to get to production 41 | - Own the health of their service 42 | - Diagnose issues 43 | 44 | Service ownership comes with some challenges: you need to train developers and still there are mistakes going to happen. 45 | Mistakes are learning opportunities. 46 | 47 | How to build infrastructure as product? Service owners - learn some Kubernetes basics, deploy and operate their own 48 | services. Reddit Infrastructure - Keep the Kubernetes cluster running, provision AWS resources, support and advise 49 | Service owners. 50 | 51 | Engineers instead of learning entire stack, had to learn only one technology - Kubernetes. If developer needs e.g. S3 - 52 | infra engineer is responsible for providing this. 53 | 54 | Batteries included - engineers do not have to worry about logging, secrets, security, ... - everything is out of the 55 | box. 56 | 57 | Extensive documentation and training for developers. Without it, you don't have a product, you have a pile of 58 | technology. 59 | 60 | > An engineer should not require deep infra experience in order to be productive. 61 | 62 | Preventing damage: resource limits, throttling, network policy, access controls, scanning for common mistakes, docker 63 | image policies 64 | -------------------------------------------------------------------------------- /conferences/aws-innovate-ai-ml-21.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # AWS Innovate: AI/ML Edition 2021 4 | 5 | - [Move and scale your ML experiments in the cloud](#move-and-scale-your-ml-experiments-in-the-cloud) 6 | - [Detect potential bias in your datasets and explain how your models predict](#detect-potential-bias-in-your-datasets-and-explain-how-your-models-predict) 7 | - [Deploy state-of-the-art ML models and solutions in a single click](#deploy-state-of-the-art-ml-models-and-solutions-in-a-single-click) 8 | 9 | Online conference took part on 24.02.2021, I participated in a couple of talks. 10 | 11 | ## Move and scale your ML experiments in the cloud 12 | 13 | Machine learning experiments (labeling the data, storage, sharing, saving, tuning parameters) can be done in Amazon 14 | SageMaker IDE - secure, scalable, compliant solution - DevOps ready solution. 15 | 16 | **How to start?** We usually start with local notebooks, which are not powerful enough. You could move your Jupiter 17 | Notebook to the cloud (doing it on your own - a lot of maintenance), we can do better. 18 | 19 | DEMO: 20 | 21 | 1. Just go to the SageMaker page on AWS 22 | 2. Open SageMaker Studio (limitation: one instance per region) 23 | 3. We are going through Standard setup: 24 | 1. Authentication method selection (SSO or IAM) 25 | 2. Permissions: which resources it can access - e.g. storage, by default SageMaker has access to any bucket with " 26 | sagemaker" in the name 27 | 3. You can make your notebook shareable 28 | 4. Network and storage definitions - VPC or Public Internet, security groups, encryption 29 | 5. You can add your tags to identify resources 30 | 4. Setup will take a few minutes 31 | 32 | You can open the application. This is literally JupyterLab. You can copy for example GitHub repo there and run the 33 | notebooks (it has git integration, so switching between branches is easy). You can easily switch machines, largest: 34 | 488GB of RAM! 35 | 36 | ![aws-innovate-ai-ml-21-1](../_images/aws-innovate-ai-ml-21-1.png) 37 | 38 | Example training: 39 | 40 | ![aws-innovate-ai-ml-21-2](../_images/aws-innovate-ai-ml-21-2.png) 41 | 42 | SageMaker is not just a notebook - it allows for data preparation, building models, training, tuning and deployment. 43 | 44 | ## Detect potential bias in your datasets and explain how your models predict 45 | 46 | Bias - unfair representation of reality, as we use datasets, there is a risk, that data we use does not represent 47 | reality. 48 | 49 | Explainability - complex models, hard to understand why model came up with a prediction (e.g. deep learning). We need to 50 | know why model came up with certain decision, e.g. medicine, legal obligations. 51 | 52 | **How to solve these issues?** 53 | 54 | We used some dataset that have the following columns: age, sex, skin colour, ... Zooming in on sex: 1/3 female, 2/3 55 | males - imbalanced. Zoom even more, 1:7 for sex earnings with >50k USD. Model can be biased towards overrepresented 56 | group. 57 | 58 | So the first approach is to visualise the data to detect bias. But AWS has something better. 59 | 60 | **Analysis using Amazon SageMaker Clarify** 61 | 62 | Bias analysis: pre-training analysis and post-training analysis. We define "potential" biased group: `faced_name="Sex"`. 63 | Results are displayed in a nice charts (many awesome metrics): 64 | 65 | ![aws-innovate-ai-ml-21-3](../_images/aws-innovate-ai-ml-21-3.png) 66 | 67 | It also outputs report in HTML and Jupyter Notebook. 68 | 69 | **Explainability** - it uses SHAP 🎉 https://github.com/slundberg/shap 70 | 71 | For explainability AWS outputs similar report: 72 | 73 | ![aws-innovate-ai-ml-21-4](../_images/aws-innovate-ai-ml-21-4.png) 74 | 75 | ## Deploy state-of-the-art ML models and solutions in a single click 76 | 77 | SageMaker Studio. Problem: text analysis, there are 60 models prepared for text analysis. We can select one, e.g. 78 | trained on Wikipedia. Then we can deploy the model, we can fine tune the model - we need to provide the dataset in a 79 | special format. Model has an endpoint, which can be tested in the Jupyter Notebook. 80 | 81 | ![aws-innovate-ai-ml-21-5](../_images/aws-innovate-ai-ml-21-5.png) 82 | 83 | We have a notebook, but we can not give it to the Product Managers, that is why we can integrate it with for example an 84 | UI. There are libraries for the integration with JavaScript. Example: banana slicer review from Amazon: 85 | 86 | ![aws-innovate-ai-ml-21-6](../_images/aws-innovate-ai-ml-21-6.png) 87 | 88 | New data flow - tool for preparing a new data. Then you can pass the data to the model to train. 89 | 90 | **Remember to shut down the endpoint because you pay for it $$$.** 91 | -------------------------------------------------------------------------------- /conferences/brown-bags.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | - [NLP - State of the Art](#nlp---state-of-the-art) 4 | - [Kanban Training](#kanban-training) 5 | 6 | ## NLP - State of the Art 7 | 8 | *By Michał Jakóbczyk* 9 | 10 | Turing Test - are you able to distinguish if you are talking to a computer or a person? It determined the direction of 11 | development of NLP. 12 | 13 | > The Man Who Mistook His Wife For a Hat - Olivier Sacks - book recommendation. 14 | 15 | Analyse sentence: 16 | 17 | ```python 18 | from spacy import displacy 19 | 20 | displacy.render(nlp("Some sentence")) 21 | ``` 22 | 23 | "They ate the pizza with anchovies" - context matters (with fishes or using fishes?). 24 | 25 | "They ate the pizza with hands" 26 | 27 | "I shot an elephant in my pyjamas" - model will refer pyjama to the elephant. 28 | 29 | "I shot an elephant, in my pyjamas" - model will refer pyjama to the person. 30 | 31 | We know about these differences! Models have difficulties. 32 | 33 | 40-50 years ago, NLP was mostly about POS tags analysis, recently is more about machine learning. 34 | 35 | Python code -> Assembler <- Machine learning model. In the end everything is Assembly. 36 | 37 | *playground.tensorflow.org* - 1 square = 1 neuron that is basically checking one if / one line. 38 | 39 | Text to number: 40 | 41 | - document vectorisation - if document contains word - 1, 0 otherwise 42 | - one-hot encoding - you can use it for encoding word position (2D matrix) - a lot of memory 43 | - word embeddings - place word in a multidimensional space 44 | - adding vectors - drawing a multidimensional sphere containing multiple words 45 | - *projector.tensorflow.org* 46 | 47 | We can compare sentences using embeddings. 48 | 49 | ```python 50 | nlp("Gave a research talk in Boston").similarity(nlp("Had a science lecture in Seattle")) 51 | ``` 52 | 53 | Training is done using input text, then every word is removed (word by word) and machine is supposed to guess missing 54 | word. 55 | 56 | GPT-3 - the biggest transformer, almost 5M$ spent on training this model 57 | 58 | ## Kanban Training 59 | 60 | *By Marcin Lelek* 61 | 62 | https://tools.kaiten.io/featureban 63 | 64 | KANBAN - card + signal, name of the board, method for implementing improvements requested by client. Created by Toyota. 65 | 66 | 3 rules: 67 | 68 | - stat with what you do now 69 | - gain agreement to evolutionary change (don't make changes against people, agree on change) 70 | - encourage acts of leadership at all levels (independent teams) 71 | 72 | General practices: 73 | 74 | - you need to have a board to visualise progress 75 | - number of items in Work In Progress is limited 76 | - manage flow - work flow management, not people optimisation 77 | - make policies explicit - define policy how to treat a card in a column, e.g. when card moves from one column to 78 | another 79 | - implement feedback loops 80 | - improve collaboratively 81 | - evolve experimentally 82 | 83 | Different levels of Kanban boards - e.g. 1 WIP per person. 84 | -------------------------------------------------------------------------------- /conferences/pycon-2022.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | - [[EN] Don’t use a lot where a little will do. A story of programming tricks you wish you invented](#en-dont-use-a-lot-where-a-little-will-do-a-story-of-programming-tricks-you-wish-you-invented) 4 | - [[EN] Effective data science teams with databooks](#en-effective-data-science-teams-with-databooks) 5 | - [[PL] Poetry - poezja pythonowych pakietów](#pl-poetry---poezja-pythonowych-pakietw) 6 | - [[EN] Interfaces in Python. The benefits and harms](#en-interfaces-in-python-the-benefits-and-harms) 7 | - [[EN] Observability in backends with Python and OpenTelemetry](#en-observability-in-backends-with-python-and-opentelemetry) 8 | - [[EN] Hitchhiker's guide to typing](#en-hitchhikers-guide-to-typing) 9 | - [[EN] Lightning talks](#en-lightning-talks) 10 | - [[PL] Dzielenie monolitu w praktyce](#pl-dzielenie-monolitu-w-praktyce) 11 | - [[EN] pytest on steroids](#en-pytest-on-steroids) 12 | - [[EN] Music information retrieval with Python](#en-music-information-retrieval-with-python) 13 | 14 | ## [EN] Don’t use a lot where a little will do. A story of programming tricks you wish you invented 15 | 16 | Regex has a debug mode - `re.DEBUG` 17 | 18 | Python has a built-in HTTP server capable of serving static files from the current directory. 19 | 20 | ## [EN] Effective data science teams with databooks 21 | 22 | `databooks` - a tool for dealing with notebooks (automatic conflict resolution, metadata stripping, pre-commit hooks, 23 | printing a notebook in a terminal, pretty printing git diff) 24 | 25 | Architecture as Code (AaC) with Python or way to become your own boss 26 | 27 | Prototyping and visualization of system architecture using code. 28 | 29 | `diagrams` - a library for creating diagrams from Python 30 | 31 | ## [PL] Poetry - poezja pythonowych pakietów 32 | 33 | Narzędzie do zarządzania zależnościami, a także do tworzenia pakietów oraz ich publikacji. 34 | 35 | Może zastąpić `pip` czy `virtualenva`. 36 | 37 | Wersjonowanie semantyczne: `major.minor.patch` 38 | 39 | ## [EN] Interfaces in Python. The benefits and harms 40 | 41 | Abstract classes in Python - ABC 42 | 43 | Sequence - any collection implementing 2 methods (length and getter) 44 | 45 | Dependency Injection - passing parameters directly to for example init method. 46 | 47 | ## [EN] Observability in backends with Python and OpenTelemetry 48 | 49 | Trace - a JSON object, can travel between services. Simple types (int, bool, lists, ...) 50 | 51 | Auto-instrumentation - install a couple of libraries, run the command, done. 52 | 53 | Manual-instrumentation - via a context manager or a decorator inside the code. 54 | 55 | Distributed tracing with queues - context of the trace is going to be part of the massage that you enqueue. 56 | 57 | Jagger - one of tools compatible with OpenTelemetry. 58 | 59 | unicorn has a separate thread for OpenTelemetry data. 60 | 61 | ## [EN] Hitchhiker's guide to typing 62 | 63 | urllib3 case study: https://sethmlarson.dev/blog/tests-arent-enough-case-study-after-adding-types-to-urllib3 64 | 65 | ## [EN] Lightning talks 66 | 67 | GitHub Actions are capable of running cron jobs. 68 | 69 | Idea: when learning a new language, rewrite an existing command line tool in selected language. 70 | 71 | ## [PL] Dzielenie monolitu w praktyce 72 | 73 | Kryteria sukcesu wydzielania mikroserwisu: chce szybko widzieć efekty, moc wycofać się w każdym momencie, testować 74 | system z ruchem produkcyjnym, ALE nie chce zepsuć produkcji, chce moc wrócić do starego rozwiązania, jak najmniej 75 | zmieniać w monolicie. 76 | 77 | Przekształcanie monolitu w mikroserwisu: wydzielenie interfejsu w monolicie, stworzenie mikroserwisu z identycznym 78 | interfejsem, dodanie nowej implementacji w monolicie korzystającej z nowego serwisu. Gdy przychodzi zapytanie możemy je 79 | wysyłać do dwóch miejsc, ostateczna odpowiedź powinna pochodzić ze starego systemu, po okresie testów przełączamy się na 80 | nowe rozwiązanie. 81 | 82 | ![pycon-2022-monolith](../_images/pycon-2022-monolith.jpeg) 83 | 84 | Strangler Pattern - nazwa pochodzi od rośliny, która pasożytuje na drzewie, wykorzystuje je żeby rosnąć w górę po czym 85 | je zabija. 86 | 87 | Działanie w Shadow Mode - wydzielenie mikroserwisu, zebranie zapytań i wyników. 88 | 89 | ## [EN] pytest on steroids 90 | 91 | Everything in pytest is a plugin. When you create a fixture you create a local plugin. 92 | 93 | ## [EN] Music information retrieval with Python 94 | 95 | `pedalbord` by Spotify - a python library for audio effects 96 | 97 | `Pyo` - audio synthesis engine, effects control, implementing loopers, used in live music 98 | 99 | ![pycon-2022-apis](../_images/pycon-2022-apis.jpeg) 100 | 101 | `ISMIR dataset` - various datasets with music, lyrics, ... 102 | 103 | `mirdata` - a Python wrapper for ISMIR datasets 104 | 105 | `Librosa` - a library for music analysis 106 | 107 | In general, there are plenty tools for music analysis, which then can be used to train ML models. 108 | 109 | ![pycon-2022-music-tagging](../_images/pycon-2022-music-tagging.jpeg) 110 | 111 | ![pycon-2022-source-separation](../_images/pycon-2022-source-separation.jpeg) 112 | 113 | ![pycon-2022-source-separation-1](../_images/pycon-2022-source-separation-1.jpeg) 114 | 115 | ![pycon-2022-transcription](../_images/pycon-2022-transcription.jpeg) 116 | 117 | Music recommendations: very complex, massive business and cultural impact: 118 | 119 | ![pycon-2022-music-recommendations](../_images/pycon-2022-music-recommendations.jpeg) 120 | 121 | Generating music - neural audio synthesis or symbolic composition (then needs to be played by a human). 122 | 123 | Links: 124 | - https://openai.com/blog/jukebox/ 125 | - https://youtu.be/bXBliLjImio 126 | - https://youtu.be/MwtVkPKx3RA 127 | - https://youtu.be/tgq1YTQ2c0s 128 | - https://magenta.tensorflow.org 129 | -------------------------------------------------------------------------------- /courses/fast-ai.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Practical Deep Learning for Coders 4 | 5 | Course -> https://course.fast.ai/ 6 | 7 | [TOC] 8 | 9 | ## Lesson 1 10 | 11 | Truth, to start with Deep Learning: 12 | 13 | - high school math is sufficient 14 | - there is no need for enormous amounts of data 15 | - no need for expensive hardware for basic usage 16 | 17 | 1961 first machine built on top of mathematical model from 1943. Heavily criticised by Minsky - example that artificial 18 | neural network could not learn simple XOR. Global academic gave up on neural networks. 19 | 20 | 1986 MIT released a paper defining requirements for building and using neural networks. Later researchers proved, that 21 | adding additional layers of neural networks is enough to approximate any mathematical model. But in fact these models 22 | were too slow and too big to be useful. 23 | 24 | **What is ML?** Like regular programming, a way to get computers to complete a specific task. Instead of telling the 25 | computer the exact steps to solve a problem, show it examples of the problem to solve and let it figure out how to solve 26 | it itself. 27 | 28 | *Neural network* - parametrised function that can solve any problem to any level of accuracy (in theory - *universal 29 | approximation theorem)*. 30 | 31 | What does it mean to train neural network? It means finding good weights. This is called **SDG**. SDG - Stochastic 32 | Gradient Descent. 33 | 34 | Neural Networks work using patterns, need labeled data and create PREDICTIONS not recommended actions. 35 | 36 | You need to be super careful what is the input data (initial bias, stereotypic data) will produce biased results. E.g. 37 | marihuana consumption is equal amon whites and blacks, but black people are mor often arrested for marijuana possession. 38 | Given biased input data will produce biased predictions, e.g. send more police officers to black neighbourhoods. 39 | 40 | Segmentation - marking areas on images (trees, cars, ...) 41 | 42 | ## Lesson 2 43 | 44 | When you want to predict a category you are facing a classification problem. Whenever you want to predict a number you 45 | are dealing with regression problem. 46 | 47 | ```python 48 | learn = cnn_learner(data, architecture, metric) 49 | ``` 50 | 51 | Architecture - e.g. *resnet32, resnet64* - name of the architecture (64 layers) - function that we are optimising. 52 | 53 | Epoch - e.g. looking at every image in the training set = 1 epoch, 1 loop 54 | 55 | Metric - function measuring quality of the model's predictions (*error_rate, accuracy*), we care about it. 56 | 57 | Loss != Metric, loss - computer uses this to update parameters, computer cares about it. For example tweaking parameters 58 | just a little might not change accuracy or error rate. 59 | 60 | Model might cheat - "I have seen this image, this is a cat", we don't want model to memorise images. That is why we need 61 | splitting into training and validation. For validating time-series, you should not removed e.g. 20% of the data, instead, 62 | drop off the end and let the model predict e.g. next 2 weeks. 63 | 64 | *Transfer learning* - using a pretrained model for a task different to what it was originally trained for. Take 65 | pretrained (initial weights), add more epochs on your specific dataset and you will end up with way more better model. 66 | 67 | *Fine tuning* - transfer learning technique where the weights of pretrained model are updated by training for additional 68 | epochs using different task to that used for pretraining. 69 | 70 | You can take advantage of pretrained feature - e.g. dog faces, patterns, etc. 71 | 72 | Computer Vision can be used for variety of problems, e.g. sound, virus analysis (data transformed into images). 73 | 74 | ![fast-ai-1](../_images/fast-ai-1.png) 75 | 76 | Set of pretrained models: https://modelzoo.co/ 77 | 78 | *How to decide if there is a relationship?* 79 | 80 | *Null hypothesis* - e.g. "no relationship between X and Y" -> gather data -> how often do we see a relationship? 81 | 82 | *P-Value* - probability of an observed result assuming that the null hypothesis is true. 83 | 84 | ## Lesson 3 85 | 86 | Square images are easier to process, you need to remember the length of only one dimension. `Squishing` is the most 87 | efficient method for resizing, because cropping removes information, adding black bars wastes computations. Another most 88 | common method is `Random Resize Crop` - few batches, different parts of the image are taken 89 | 90 | ImageClassifierCleaner - utility tool (GUI) for finding examples, classifier is least confident about. You can manually 91 | improve labelling. 92 | 93 | `VBox` - you can group multiple widgets together and create prototype application in notebook. 94 | 95 | `viola` - plugin for hiding cells with code, only inputs and outputs are visible. Add `viola` to the URL, and it will 96 | display an application-like website in the browser. Great for prototyping. 97 | 98 | mybinder.org - you can turn notebook from GitHub into a publicly available web application. 99 | 100 | *Healthy skin* example - bing returns images of a young white woman - bias! 101 | 102 | Book recommendation: *Building Machine Learning Powered Applications* 103 | 104 | Feedback loop - e.g. predictive policing - system that sends police - feedback loops can result in negative implications 105 | of that bias getting worse and worse - e.g. you send police to the same place over and over. 106 | 107 | FastPages - dump notebook into a page. 108 | 109 | Recognising hand written digits (MNIST) was considered challenging problem ~20 years ago. Baseline idea: compare model / 110 | ideal number with input - for MNIST, calculate average of the training set, on validation set - calculate distance (~95% 111 | accuracy). Baseline should be something simple to implement - then you build something on top of it. 112 | 113 | Broadcasting - if shapes of 2 elements don't match, e.g. A (1010, 28, 28) - B (28, 28), B will be subtracted from every 114 | 1010 items from A. 115 | 116 | PyTorch has engine for calculating derivatives. In PyTorch `_` at the end of the method means "method in place". 117 | 118 | Learning rate - size of a step in gradient descent 119 | 120 | ## Plant Pathology 121 | 122 | https://www.kaggle.com/c/plant-pathology-2021-fgvc8/overview 123 | 124 | ```python 125 | import csv 126 | from fastai.vision.all import * 127 | from fastai.metrics import error_rate, 128 | accuracy 129 | 130 | path = Path("/kaggle/input/plant-pathology-2021-fgvc8") 131 | 132 | # Prepare data, labels are stored separately: 133 | with open(path / "train.csv", mode='r') as csv_file: 134 | csv_reader = csv.DictReader(csv_file) 135 | 136 | train_labels = { 137 | row["image"]: row["labels"] 138 | for row in csv_reader 139 | } 140 | 141 | 142 | # Function used for labeling images: 143 | def label_func(file_path: Path) -> str: 144 | return train_labels[str(file_path).split('/')[-1]] 145 | 146 | 147 | # Read data: 148 | data_block = DataBlock( 149 | blocks=(ImageBlock, CategoryBlock), 150 | get_items=get_image_files, 151 | get_y=label_func, 152 | item_tfms=Resize(224) 153 | ) 154 | 155 | # DataBlock to DataLoader: 156 | data_loaders = data_block.dataloaders(path / "train_images") 157 | 158 | # Available classes: 159 | data_loaders.vocab 160 | 161 | # Few example images: 162 | data_loaders.show_batch() 163 | 164 | # ResNet34 architecture for image classification: 165 | learner = cnn_learner(data_loaders, models.resnet34, metrics=error_rate) 166 | 167 | # 4 epochs, unfortunately one epoch takes ~1h most probably because of incorrect use of 'item_tfms' in DataBlock, which disables GPU usage: 168 | learner.fine_tune(4) 169 | 170 | # Model validation, this model achieved 0.62 error_rate. 171 | interpretation = ClassificationInterpretation.from_learner(learner) 172 | interpretation.plot_confusion_matrix() 173 | interpretation.plot_top_losses(5, nrows=1, figsize=(25, 5)) 174 | 175 | # Saving model: 176 | learner.export() 177 | ``` 178 | 179 | -------------------------------------------------------------------------------- /patterns/abbreviations.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Abbreviations 4 | 5 | - [SOLID](#solid) 6 | - [DRY - Don't Repeat Yourself](#dry---dont-repeat-yourself) 7 | - [KISS - Keep It Simple, Stupid](#kiss---keep-it-simple-stupid) 8 | - [ACID](#acid) 9 | - [BASE](#base) 10 | - [CAP](#cap) 11 | - [NF](#nf) 12 | 13 | ## SOLID 14 | 15 | ### SRP - Single Responsibility Principle 16 | 17 | A class should have only one reason to change, so in order to reduce reasons for modifications - one class should have 18 | one responsibility. It is a bad practise to create classes doing everything. 19 | 20 | Why is it so important that class has only one reason to change? If class have more than one responsibility they become 21 | coupled and this might lead to surprising consequences like one change breaks another functionality. 22 | 23 | You can avoid these problems by asking a simple question before you make any changes: What is the responsibility of your 24 | class / component / micro-service? If your answer includes the word “and”, you’re most likely breaking the single 25 | responsibility principle. 26 | 27 | ### OCP - Open-Closed Principle 28 | 29 | Classes, modules, functions, etc. should be open to extension but closed to modification. 30 | 31 | Code should be extensible and adaptable to new requirements. In other words, we should be able to add new system 32 | functionality without having to modify the existing code. We should add functionality only by writing new code. 33 | 34 | If we want to add a new thing to the application and we have to modify the "old", existing code to achieve this, it is 35 | quite likely that it was not written in the best way. Ideally, new behaviors are simply added. 36 | 37 | ### LSP - Liskov Substitution Principle 38 | 39 | This rule deals with the correct use of inheritance and states that wherever we pass an object of a base class, we 40 | should be able to pass an object of a class inheriting from that class. 41 | 42 | Example of violation: 43 | 44 | ```python 45 | class A: 46 | def foo() -> str: 47 | return "foo" 48 | 49 | 50 | class B(A): 51 | def foo(bar: str) -> str: 52 | return f"foo {bar}" 53 | ``` 54 | 55 | B is not taking the same arguments, meaning A and B are not compatible. A can not be used instead of B, and B can not be 56 | used instead of A. 57 | 58 | ### ISP - Interface Segregation Principle 59 | 60 | Clients should not be forced to depend upon interfaces that they do not use. ISP splits interfaces that are very large 61 | into smaller and more specific ones so that clients will only have to know about the methods that are of interest to 62 | them. 63 | 64 | Example of violation: 65 | 66 | ```python 67 | class Shape: 68 | def area() -> float: 69 | raise NotImplementedError 70 | 71 | def volume() -> float(): 72 | raise NotImplementedError 73 | ``` 74 | 75 | 2D triangle does not have volume, hence it would need to implement interface that is not needed. In order to solve this, 76 | there should be multiple interfaces: Shape and 3DShape. 77 | 78 | ### DIP - Dependency Inversion Principle 79 | 80 | High-level modules, which provide complex logic, should be easily reusable and unaffected by changes in low-level 81 | modules, which provide utility features. To achieve that, you need to introduce an abstraction that decouples the 82 | high-level and low-level modules from each other. 83 | 84 | > Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the 85 | > low-level module, but they should depend on abstractions. 86 | 87 | For example password reminder should not have knowledge about database provider (low level information). 88 | 89 | ## DRY - Don't Repeat Yourself 90 | 91 | "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system". When the DRY 92 | principle is applied successfully, a modification of any single element of a system does not require a change in other 93 | logically unrelated elements. 94 | 95 | ## KISS - Keep It Simple, Stupid 96 | 97 | The KISS principle states that most systems work best if they are kept simple rather than made complicated; therefore, 98 | simplicity should be a key goal in design, and unnecessary complexity should be avoided. 99 | 100 | ## ACID 101 | 102 | ### Atomicity 103 | 104 | Each transaction is either properly carried out or the process halts and the database reverts back to the state before 105 | the transaction started. This ensures that all data in the database is valid. 106 | 107 | ### Consistency 108 | 109 | A processed transaction will never endanger the structural integrity of the database. Database is always in consistent 110 | state. 111 | 112 | ### Isolation 113 | 114 | Transactions cannot compromise the integrity of other transactions by interacting with them while they are still in 115 | progress. 116 | 117 | ### Durability 118 | 119 | The data related to the completed transaction will persist even in the cases of network or power outages. If a 120 | transaction fails, it will not impact the manipulated data. 121 | 122 | ## BASE 123 | 124 | ### Basically Available 125 | 126 | Ensure availability of data by spreading and replicating it across the nodes of the database cluster - this is not done 127 | immediately. 128 | 129 | ### Soft State 130 | 131 | Due to the lack of immediate consistency, data values may change over time. The state of the system could change over 132 | time, so even during times without input there may be changes going on due to 'eventual consistency', thus the state of 133 | the system is always 'soft'. 134 | 135 | ### Eventually Consistent 136 | 137 | The system will *eventually* become consistent once it stops receiving input. The data will propagate to everywhere it 138 | should sooner or later, but the system will continue to receive input and is not checking the consistency of every 139 | transaction before it moves onto the next one. 140 | 141 | ## CAP 142 | 143 | In theoretical computer science, the CAP theorem states that it is impossible for a distributed data store to 144 | simultaneously provide more than two out of the following three guarantees: 145 | 146 | ### Consistency 147 | 148 | Every read receives the most recent write or an error. Refers to whether a system operates fully or not. Does the system 149 | reliably follow the established rules within its programming according to those defined rules? Do all nodes within a 150 | cluster see all the data they are supposed to? This is the same idea presented in ACID. 151 | 152 | ### Availability 153 | 154 | Every request receives a (non-error) response, without the guarantee that it contains the most recent write. Is the 155 | given service or system available when requested? Does each request get a response outside of failure or success? 156 | 157 | ### Partition Tolerance 158 | 159 | Represents the fact that a given system continues to operate even under circumstances of data loss or system failure. A 160 | single node failure should not cause the entire system to collapse. 161 | 162 | ## NF 163 | 164 | Database normalisation is the process of structuring a database, usually a relational database, in accordance with a 165 | series of so-called normal forms in order to reduce data redundancy and improve data integrity. 166 | 167 | ### 1NF 168 | 169 | To satisfy 1NF, the values in each column of a table must be atomic. 170 | 171 | ### 2NF 172 | 173 | Must be in 1NF + single column primary key (no composite keys). 174 | 175 | ### 3NF 176 | 177 | Must be in 2NF + no transitive functional dependencies. 178 | 179 | Transitive Functional Dependencies - when changing a non-key column, might cause any of the other non-key columns to 180 | change. For example: 181 | 182 | ![3nf-violation](../_images/3nf-violation.png) 183 | -------------------------------------------------------------------------------- /patterns/architecture.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Architecture Patterns 4 | 5 | - [Command and Query Responsibility Segregation (CQRS)](#command-and-query-responsibility-segregation-cqrs) 6 | - [Reporting Database](#reporting-database) 7 | - [Event Sourcing](#event-sourcing) 8 | - [Saga](#saga) 9 | 10 | ## Command and Query Responsibility Segregation (CQRS) 11 | 12 | Based on: https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs, https://martinfowler.com/bliki/CQRS.html 13 | , https://bulldogjob.pl/articles/122-cqrs-i-event-sourcing-czyli-latwa-droga-do-skalowalnosci-naszych-systemow_ 14 | 15 | This pattern separates read and update operations for a data store. Traditionally the same data model is used to query 16 | and update a database. This might work well but for simple CRUD applications. For more complex applications, where there 17 | are more advanced operations on read and write sides CQRS might be a better idea. 18 | 19 | Commands update data, queries read data. Commands should be *task based*, rather than *data centric* (book hotel room 20 | instead of set `reservation_status` to `reserved`). Queries *never* modify the database. 21 | 22 | Usually whenever command updates data it is also publishing an event and this needs to be done within a single 23 | transaction. 24 | 25 | ![patterns-architecture-cqrs-martin-fowler](../_images/patterns-architecture-cqrs-martin-fowler.png) 26 | 27 | CQRS: 28 | 29 | - you are able to scale Command and Query independently 30 | - separate models for updating and querying might lead to eventual consistency 31 | - suited for complex domains 32 | 33 | ## Reporting Database 34 | 35 | Based on: https://martinfowler.com/bliki/ReportingDatabase.html 36 | 37 | Set up second database for reporting purposes, this database is completely different from the operational (application) 38 | database. 39 | 40 | Reporting Database: 41 | 42 | - designed specifically for reports 43 | 44 | - can be denormalized, usually read-only - redundant information might speed up queries 45 | - queries on the database don't add to the load on the operational database 46 | - additional data might be derived from the operational database 47 | - needs to be synced somehow with the main database (eg. sync data overnight or sync using events) 48 | 49 | ## Event Sourcing 50 | 51 | Based on: https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing 52 | , https://microservices.io/patterns/data/event-sourcing.html 53 | 54 | > How to reliably/atomically update the database and publish messages/events? 55 | 56 | Instead of maintaining current state, application can have a log of state changes. Whenever the state of a business 57 | entity changes, a new event is appended to the list of events. Since saving an event is a single operation, it is 58 | inherently atomic. The application reconstructs an entity’s current state by replaying the events. 59 | 60 | The event log also behaves like message broker. When a service saves an event in the event store, it is delivered to all 61 | interested subscribers. 62 | 63 | > Event sourcing is commonly combined with the CQRS pattern by performing the data management tasks in response to the 64 | > events, and by materialising views from the stored events. 65 | 66 | In order to maintain consistency in multi-threaded applications, adding a timestamp to every event might help in 67 | resolving issues, but not in all cases. Better approach is to label each event with an incremental identifier. If two 68 | actions attempt to add events for the same entity at the same time, the event store can reject an event that matches an 69 | existing entity identifier. 70 | 71 | ![patterns-architecture-event-sourcing-overview-microsoft](../_images/patterns-architecture-event-sourcing-overview.png) 72 | 73 | This pattern is useful when: 74 | 75 | - you want to capture intent, purpose, or reason in the data 76 | - you want to record events that occur, and be able to replay them to restore the state of a system, roll back changes, 77 | or keep a history and audit log 78 | 79 | Not useful when: 80 | 81 | - small problems 82 | - consistency and real-time updates to the views of the data are required 83 | - history, and capabilities to roll back and replay actions are not required 84 | 85 | Example: banking system - list of all transactions, basing on these transactions your total balance is calculated. 86 | 87 | ## Saga 88 | 89 | Based on: https://microservices.io/patterns/data/saga.html 90 | 91 | In a design where each service has its own database, sometimes transactions have to span multiple services, hence local 92 | ACID transaction is not an option. 93 | 94 | A solution to this problem is *Saga* - a sequence of local transactions. Each local transaction updates the database and 95 | publishes a message or event to trigger the next local transaction in the saga. If a local transaction fails because it 96 | violates a business rule then the saga executes a series of compensating transactions that undo the changes that were 97 | made by the preceding local transactions. 98 | 99 | For example: Service A creates a new Order with PENDING state and publishes an event that is consumed by another service 100 | B, service B responds with an event to service A. Service A accepts or rejects new Order. 101 | 102 | DON'T: Based on `Chapter 17: Microservices Architecture` @ `Fundamentals of Software Architecture`: 103 | 104 | > Don't do transactions in microservices - fix granularity instead. 105 | -------------------------------------------------------------------------------- /teaching/python-intermediate/README.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Python Intermediate 4 | 5 | Repository with the code and tasks: https://github.com/pkardas/shapes 6 | 7 | -------------------------------------------------------------------------------- /teaching/python-intermediate/presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/teaching/python-intermediate/presentation.pdf -------------------------------------------------------------------------------- /teaching/python-intro/README.md: -------------------------------------------------------------------------------- 1 | [go back](https://github.com/pkardas/learning) 2 | 3 | # Introduction to Programming: Python for beginners 4 | 5 | This folder contains the presentation and the notebook used during "Introduction to Programming: Python for beginners" classes. Training was intended for people with no prior programming skills. Each training was scheduled for 2 hours. 6 | 7 | `presentation` - meeting agenda, topics, theory, examples 8 | 9 | `notebook` - Jupyter Notebook with assignments, audience was supposed to fill in the gaps using provided theory and examples. 10 | 11 | -------------------------------------------------------------------------------- /teaching/python-intro/presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkardas/notes/7b0d56be00b4e6246aea191b1fc27442e1ef5c12/teaching/python-intro/presentation.pdf --------------------------------------------------------------------------------