├── .gitignore ├── 0x00-python_variable_annotations ├── 0-add.py ├── 1-concat.py ├── 100-safe_first_element.py ├── 101-safely_get_value.py ├── 102-type_checking.py ├── 2-floor.py ├── 3-to_str.py ├── 4-define_variables.py ├── 5-sum_list.py ├── 6-sum_mixed_list.py ├── 7-to_kv.py ├── 8-make_multiplier.py ├── 9-element_length.py └── README.md ├── 0x01-python_async_function ├── 0-basic_async_syntax.py ├── 1-concurrent_coroutines.py ├── 2-measure_runtime.py ├── 3-tasks.py ├── 4-tasks.py └── README.md ├── 0x02-python_async_comprehension ├── 0-async_generator.py ├── 1-async_comprehension.py ├── 2-measure_runtime.py └── README.md ├── 0x03-Unittests_and_integration_tests ├── README.md ├── client.py ├── fixtures.py ├── test_client.py ├── test_utils.py └── utils.py ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/0-add.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 0's module. 3 | ''' 4 | 5 | 6 | def add(a: float, b: float) -> float: 7 | '''Adds two floating-point numbers. 8 | ''' 9 | return a + b 10 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/1-concat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 1's module. 3 | ''' 4 | 5 | 6 | def concat(str1: str, str2: str) -> str: 7 | '''Concatenates two strings. 8 | ''' 9 | return str1 + str2 10 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/100-safe_first_element.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 10's module. 3 | ''' 4 | from typing import Any, Sequence, Union 5 | 6 | 7 | def safe_first_element(lst: Sequence[Any]) -> Union[Any, None]: 8 | '''Retrieves the first element of a sequence if it exists. 9 | ''' 10 | if lst: 11 | return lst[0] 12 | else: 13 | return None 14 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/101-safely_get_value.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 11's module. 3 | ''' 4 | from typing import Any, Mapping, Union, TypeVar 5 | 6 | 7 | T = TypeVar('T') 8 | Res = Union[Any, T] 9 | Def = Union[T, None] 10 | 11 | 12 | def safely_get_value(dct: Mapping, key: Any, default: Def = None) -> Res: 13 | '''Retrieves a value from a dict using a given key. 14 | ''' 15 | if key in dct: 16 | return dct[key] 17 | else: 18 | return default 19 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/102-type_checking.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 12's module. 3 | ''' 4 | from typing import List, Tuple 5 | 6 | 7 | def zoom_array(lst: Tuple, factor: int = 2) -> List: 8 | '''Creates multiple copies of items in a tuple. 9 | ''' 10 | zoomed_in: List = [ 11 | item for item in lst 12 | for i in range(int(factor)) 13 | ] 14 | return zoomed_in 15 | 16 | 17 | array = (12, 72, 91) 18 | 19 | zoom_2x = zoom_array(array) 20 | 21 | zoom_3x = zoom_array(array, 3) 22 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/2-floor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 2's module. 3 | ''' 4 | 5 | 6 | def floor(a: float) -> int: 7 | '''Computes the floor of a floating-point number. 8 | ''' 9 | return int(a) 10 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/3-to_str.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 3's module. 3 | ''' 4 | 5 | 6 | def to_str(n: float) -> str: 7 | '''Converts a floating-point number to a string. 8 | ''' 9 | return str(n) 10 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/4-define_variables.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 4's module. 3 | ''' 4 | 5 | 6 | a: int = 1 7 | pi: float = 3.14 8 | i_understand_annotations: bool = True 9 | school: str = 'Holberton' 10 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/5-sum_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 5's module. 3 | ''' 4 | from typing import List 5 | 6 | 7 | def sum_list(input_list: List[float]) -> float: 8 | '''Computes the sum of a list of floating-point numbers. 9 | ''' 10 | return float(sum(input_list)) 11 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/6-sum_mixed_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 6's module. 3 | ''' 4 | from typing import List, Union 5 | 6 | 7 | def sum_mixed_list(mxd_lst: List[Union[int, float]]) -> float: 8 | '''Computes the sum of a list of integers and floating-point numbers. 9 | ''' 10 | return float(sum(mxd_lst)) 11 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/7-to_kv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 7's module. 3 | ''' 4 | from typing import Union, Tuple 5 | 6 | 7 | def to_kv(k: str, v: Union[int, float]) -> Tuple[str, float]: 8 | '''Converts a key and its value to a tuple of the key and 9 | the square of its value. 10 | ''' 11 | return (k, float(v**2)) 12 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/8-make_multiplier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 8's module. 3 | ''' 4 | from typing import Callable 5 | 6 | 7 | def make_multiplier(multiplier: float) -> Callable[[float], float]: 8 | '''Creates a multiplier function. 9 | ''' 10 | return lambda x: x * multiplier 11 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/9-element_length.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 9's module. 3 | ''' 4 | from typing import Iterable, List, Sequence, Tuple 5 | 6 | 7 | def element_length(lst: Iterable[Sequence]) -> List[Tuple[Sequence, int]]: 8 | '''Computes the length of a list of sequences. 9 | ''' 10 | return [(i, len(i)) for i in lst] 11 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/README.md: -------------------------------------------------------------------------------- 1 | # Variable (Type) Annotations 2 | 3 | This project contains tasks for learning to use variable/type annotations in Python 3. 4 | 5 | ## Tasks To Complete 6 | 7 | + [x] 0. **Basic annotations - add**
[0-add.py](0-add.py) contains a type-annotated function `add` that takes a float `a` and a float `b` as arguments and returns their sum as a float.. 8 | 9 | + [x] 1. **Basic annotations - concat**
[1-concat.py](1-concat.py) contains a type-annotated function `concat` that takes a string `str1` and a string `str2` as arguments and returns a concatenated string. 10 | 11 | + [x] 2. **Basic annotations - floor**
[2-floor.py](2-floor.py) contains a type-annotated function `floor` which takes a float `n` as argument and returns the floor of the float. 12 | 13 | + [x] 3. **Basic annotations - to string**
[3-to_str.py](3-to_str.py) contains a type-annotated function `to_str` that takes a float `n` as argument and returns the string representation of the float. 14 | 15 | + [x] 4. **Define variables**
[4-define_variables.py](4-define_variables.py) contains a script that define and annotate the following variables with the specified values: 16 | + `a`, an integer with a value of 1. 17 | + `pi`, a float with a value of 3.14. 18 | + `i_understand_annotations`, a boolean with a value of True. 19 | + `school`, a string with a value of “Holberton”. 20 | 21 | + [x] 5. **Complex types - list of floats**
[5-sum_list.py](5-sum_list.py) contains a type-annotated function `sum_list` which takes a list `input_list` of floats as argument and returns their sum as a float. 22 | 23 | + [x] 6. **Complex types - mixed list**
[6-sum_mixed_list.py](6-sum_mixed_list.py) contains a type-annotated function `sum_mixed_list` which takes a list `mxd_lst` of integers and floats and returns their sum as a float. 24 | 25 | + [x] 7. **Complex types - string and int/float to tuple**
[7-to_kv.py](7-to_kv.py) contains a type-annotated function `to_kv` that takes a string `k` and an int OR float `v` as arguments and returns a tuple. The first element of the tuple is the string `k`. The second element is the square of the int/float `v` and should be annotated as a float. 26 | 27 | + [x] 8. **Complex types - functions**
[8-make_multiplier.py](8-make_multiplier.py) contains a type-annotated function `make_multiplier` that takes a float `multiplier` as argument and returns a function that multiplies a float by `multiplier`. 28 | 29 | + [x] 9. **Let's duck type an iterable object**
[9-element_length.py](9-element_length.py) contains an annotation of the function's (shown below) parameters and return values with the appropriate types. 30 | ```python 31 | def element_length(lst): 32 | return [(i, len(i)) for i in lst] 33 | ``` 34 | 35 | + [x] 10. **Duck typing - first element of a sequence**
[100-safe_first_element.py](100-safe_first_element.py) contains an augmentation of the following code with the correct duck-typed annotations: 36 | ```python 37 | # The types of the elements of the input are not know 38 | def safe_first_element(lst): 39 | if lst: 40 | return lst[0] 41 | else: 42 | return None 43 | ``` 44 | 45 | + [x] 11. **More involved type annotations**
[101-safely_get_value.py](101-safely_get_value.py) contains a script that includes the code below with type annotations added to it. 46 | ```python 47 | def safely_get_value(dct, key, default = None): 48 | if key in dct: 49 | return dct[key] 50 | else: 51 | return default 52 | ``` 53 | 54 | + [x] 12. **Type Checking**
[102-type_checking.py](102-type_checking.py) contains the code below and uses `mypy` to validate it and apply any necessary changes. 55 | ```python 56 | def zoom_array(lst: Tuple, factor: int = 2) -> Tuple: 57 | zoomed_in: Tuple = [ 58 | item for item in lst 59 | for i in range(factor) 60 | ] 61 | return zoomed_in 62 | 63 | 64 | array = [12, 72, 91] 65 | 66 | zoom_2x = zoom_array(array) 67 | 68 | zoom_3x = zoom_array(array, 3.0) 69 | ``` 70 | -------------------------------------------------------------------------------- /0x01-python_async_function/0-basic_async_syntax.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 0's module. 3 | ''' 4 | import asyncio 5 | import random 6 | 7 | 8 | async def wait_random(max_delay: int = 10) -> float: 9 | '''Waits for a random number of seconds. 10 | ''' 11 | wait_time = random.random() * max_delay 12 | await asyncio.sleep(wait_time) 13 | return wait_time 14 | -------------------------------------------------------------------------------- /0x01-python_async_function/1-concurrent_coroutines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 1's module. 3 | ''' 4 | import asyncio 5 | from typing import List 6 | 7 | 8 | wait_random = __import__('0-basic_async_syntax').wait_random 9 | 10 | 11 | async def wait_n(n: int, max_delay: int) -> List[float]: 12 | '''Executes wait_random n times. 13 | ''' 14 | wait_times = await asyncio.gather( 15 | *tuple(map(lambda _: wait_random(max_delay), range(n))) 16 | ) 17 | return sorted(wait_times) 18 | -------------------------------------------------------------------------------- /0x01-python_async_function/2-measure_runtime.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 2's module. 3 | ''' 4 | import asyncio 5 | import time 6 | 7 | 8 | wait_n = __import__('1-concurrent_coroutines').wait_n 9 | 10 | 11 | def measure_time(n: int, max_delay: int) -> float: 12 | '''Computes the average runtime of wait_n. 13 | ''' 14 | start_time = time.time() 15 | asyncio.run(wait_n(n, max_delay)) 16 | return (time.time() - start_time) / n 17 | -------------------------------------------------------------------------------- /0x01-python_async_function/3-tasks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 3's module. 3 | ''' 4 | import asyncio 5 | 6 | 7 | wait_random = __import__('0-basic_async_syntax').wait_random 8 | 9 | 10 | def task_wait_random(max_delay: int) -> asyncio.Task: 11 | '''Creates an asynchronous task for wait_random. 12 | ''' 13 | return asyncio.create_task(wait_random(max_delay)) 14 | -------------------------------------------------------------------------------- /0x01-python_async_function/4-tasks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 4's module. 3 | ''' 4 | import asyncio 5 | from typing import List 6 | 7 | 8 | task_wait_random = __import__('3-tasks').task_wait_random 9 | 10 | 11 | async def task_wait_n(n: int, max_delay: int) -> List[float]: 12 | '''Executes task_wait_random n times. 13 | ''' 14 | wait_times = await asyncio.gather( 15 | *tuple(map(lambda _: task_wait_random(max_delay), range(n))) 16 | ) 17 | return sorted(wait_times) 18 | -------------------------------------------------------------------------------- /0x01-python_async_function/README.md: -------------------------------------------------------------------------------- 1 | # Async 2 | 3 | This project contains tasks for learning to use asynchronous code in Python 3. 4 | 5 | ## Tasks To Complete 6 | 7 | + [x] 0. **The basics of async**
[0-basic_async_syntax.py](0-basic_async_syntax.py) contains an asynchronous coroutine that takes in an integer argument (`max_delay`, with a default value of 10) named `wait_random` that waits for a random delay between 0 and `max_delay` (included and float value) seconds and eventually returns it. The `random` module should be used. 8 | 9 | + [x] 1. **Let's execute multiple coroutines at the same time with async**
[1-concurrent_coroutines.py](1-concurrent_coroutines.py) contains a script that meets the following requirements: 10 | + Import `wait_random` from the previous python file that you’ve written and write an async routine called `wait_n` that takes in 2 int arguments (in this order): `n` and `max_delay`. You will spawn wait_random n times with the specified `max_delay`. 11 | + `wait_n` should return the list of all the delays (float values). The list of the delays should be in ascending order without using `sort()` because of concurrency. 12 | 13 | + [x] 2. **Measure the runtime**
[2-measure_runtime.py](2-measure_runtime.py) contains a script that meets the following requirements: 14 | + From the previous file, import `wait_n` into [2-measure_runtime.py](2-measure_runtime.py). 15 | + Create a `measure_time` function with integers `n` and `max_delay` as arguments that measures the total execution time for `wait_n(n, max_delay)`, and returns `total_time / n`. Your function should return a float. 16 | + Use the `time` module to measure an approximate elapsed time. 17 | 18 | + [x] 3. **Tasks**
[3-tasks.py](3-tasks.py) contains a script that meets the following requirements: 19 | + Import `wait_random` from [0-basic_async_syntax](0-basic_async_syntax). 20 | + Write a function (do not create an async function, use the regular function syntax to do this) `task_wait_random` that takes an integer `max_delay` and returns a `asyncio.Task`. 21 | 22 | + [x] 4. **Tasks**
[4-tasks.py](4-tasks.py) contains a script that meets the following requirements: 23 | + Take the code from `wait_n` and alter it into a new function `task_wait_n`. The code is nearly identical to `wait_n` except `task_wait_random` is being called. 24 | -------------------------------------------------------------------------------- /0x02-python_async_comprehension/0-async_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 0's module. 3 | ''' 4 | import asyncio 5 | import random 6 | from typing import Generator 7 | 8 | 9 | async def async_generator() -> Generator[float, None, None]: 10 | '''Generates a sequence of 10 numbers. 11 | ''' 12 | for _ in range(10): 13 | await asyncio.sleep(1) 14 | yield random.random() * 10 15 | -------------------------------------------------------------------------------- /0x02-python_async_comprehension/1-async_comprehension.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 1's module. 3 | ''' 4 | from typing import List 5 | from importlib import import_module as using 6 | 7 | 8 | async_generator = using('0-async_generator').async_generator 9 | 10 | 11 | async def async_comprehension() -> List[float]: 12 | '''Creates a list of 10 numbers from a 10-number generator. 13 | ''' 14 | return [num async for num in async_generator()] 15 | -------------------------------------------------------------------------------- /0x02-python_async_comprehension/2-measure_runtime.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | '''Task 2's module. 3 | ''' 4 | import asyncio 5 | import time 6 | from importlib import import_module as using 7 | 8 | 9 | async_comprehension = using('1-async_comprehension').async_comprehension 10 | 11 | 12 | async def measure_runtime() -> float: 13 | '''Executes async_comprehension 4 times and measures the 14 | total execution time. 15 | ''' 16 | start_time = time.time() 17 | await asyncio.gather(*(async_comprehension() for _ in range(4))) 18 | return time.time() - start_time 19 | -------------------------------------------------------------------------------- /0x02-python_async_comprehension/README.md: -------------------------------------------------------------------------------- 1 | # Async Comprehension 2 | 3 | This project contains tasks for learning to use asynchronous comprehensions in Python 3. 4 | 5 | ## Tasks To Complete 6 | 7 | + [x] 0. **Async Generator**
[0-async_generator.py](0-async_generator.py) contains an asynchronous coroutine called `async_generator` that takes no arguments and meets the following requirements: 8 | + The coroutine will loop 10 times, each time asynchronously wait 1 second, then yield a random number between 0 and 10. 9 | + Use the `random` module. 10 | 11 | + [x] 1. **Async Comprehensions**
[1-async_comprehension.py](1-async_comprehension.py) contains a script that meets the following requirements: 12 | + Import `async_generator` from the previous task and then write a coroutine called `async_comprehension` that takes no arguments. 13 | + The coroutine will collect 10 random numbers using an async comprehensing over `async_generator`, then return the 10 random numbers. 14 | 15 | + [x] 2. **Run time for four parallel comprehensions**
[2-measure_runtime.py](2-measure_runtime.py) contains a script that meets the following requirements: 16 | + Import `async_comprehension` from the previous file and write a `measure_runtime` coroutine that will execute `async_comprehension` four times in parallel using `asyncio.gather`. 17 | + `measure_runtime` should measure the total runtime and return it. 18 | -------------------------------------------------------------------------------- /0x03-Unittests_and_integration_tests/README.md: -------------------------------------------------------------------------------- 1 | # Unittests and Integration Tests 2 | 3 | This project contains tasks for learning to write unittests and integration tests in Python 3. 4 | 5 | ## Required Modules 6 | 7 | + parameterized. 8 | 9 | ## Tasks To Complete 10 | 11 | + [x] 0. **Parameterize a unit test**
[test_utils.py](test_utils.py) contains a python module that meets the following requirements: 12 | + Familiarize yourself with the `utils.access_nested_map` function and understand its purpose. Play with it in the Python console to make sure you understand. 13 | + In this task you will write the first unit test for `utils.access_nested_map`. 14 | + Create a `TestAccessNestedMap` class that inherits from `unittest.TestCase`. 15 | + Implement the `TestAccessNestedMap.test_access_nested_map` method to test that the method returns what it is supposed to. 16 | + Decorate the method with `@parameterized.expand` to test the function for following inputs: 17 | ``` 18 | nested_map={"a": 1}, path=("a",) 19 | nested_map={"a": {"b": 2}}, path=("a",) 20 | nested_map={"a": {"b": 2}}, path=("a", "b") 21 | ``` 22 | + For each of these inputs, test with `assertEqual` that the function returns the expected result. 23 | + The body of the test method should not be longer than 2 lines. 24 | 25 | + [x] 1. **Parameterize a unit test**
[test_utils.py](test_utils.py) contains a python module that meets the following requirements: 26 | + Implement `TestAccessNestedMap.test_access_nested_map_exception`. Use the `assertRaises` context manager to test that a `KeyError` is raised for the following inputs (use `@parameterized.expand`): 27 | ``` 28 | nested_map={}, path=("a",) 29 | nested_map={"a": 1}, path=("a", "b") 30 | ``` 31 | + Also make sure that the exception message is as expected. 32 | 33 | + [x] 2. **Mock HTTP calls**
[test_utils.py](test_utils.py) contains a python module that meets the following requirements: 34 | + Familiarize yourself with the `utils.get_json` function. 35 | + Define the `TestGetJson(unittest.TestCase)` class and implement the `TestGetJson.test_get_json` method to test that `utils.get_json` returns the expected result. 36 | + We don’t want to make any actual external HTTP calls. Use `unittest.mock.patch` to patch `requests.get`. Make sure it returns a `Mock` object with a `json` method that returns `test_payload` which you parametrize alongside the `test_url` that you will pass to `get_json` with the following inputs: 37 | ``` 38 | test_url="http://example.com", test_payload={"payload": True} 39 | test_url="http://holberton.io", test_payload={"payload": False} 40 | ``` 41 | + Test that the mocked `get` method was called exactly once (per input) with `test_url` as argument. 42 | + Test that the output of `get_json` is equal to `test_payload`. 43 | 44 | + [x] 3. **Parameterize and patch**
[test_utils.py](test_utils.py) contains a python module that meets the following requirements: 45 | + Read about memoization and familiarize yourself with the `utils.memoize` decorator. 46 | + Implement the `TestMemoize(unittest.TestCase)` class with a `test_memoize` method. 47 | + Inside `test_memoize`, define the following class: 48 | ```py 49 | class TestClass: 50 | 51 | def a_method(self): 52 | return 42 53 | 54 | @memoize 55 | def a_property(self): 56 | return self.a_method() 57 | ``` 58 | + Use `unittest.mock.patch` to mock `a_method`. Test that when calling `a_property` twice, the correct result is returned but `a_method` is only called once using `assert_called_once`. 59 | 60 | + [x] 4. **Parameterize and patch as decorators**
[test_client.py](test_client.py) contains a python module that meets the following requirements: 61 | + Familiarize yourself with the `client.GithubOrgClient` class. 62 | + Declare the `TestGithubOrgClient(unittest.TestCase)` class and implement the `test_org` method. 63 | + This method should test that `GithubOrgClient.org` returns the correct value. 64 | + Use `@patch` as a decorator to make sure `get_json` is called once with the expected argument but make sure it is not executed. 65 | + Use `@parameterized.expand` as a decorator to parametrize the test with a couple of `org` examples to pass to `GithubOrgClient`, in this order: 66 | + `google`. 67 | + `abc`. 68 | + No external HTTP calls should be made. 69 | 70 | + [x] 5. **Mocking a property**
[test_client.py](test_client.py) contains a python module that meets the following requirements: 71 | + `memoize` turns methods into properties. Read up on how to mock a property. 72 | + Implement the `test_public_repos_url` method to unit-test `GithubOrgClient._public_repos_url`. 73 | + Use `patch` as a context manager to patch `GithubOrgClient.org` and make it return a known payload. 74 | + Test that the result of `_public_repos_url` is the expected one based on the mocked payload. 75 | 76 | + [x] 6. **More patching**
[test_client.py](test_client.py) contains a python module that meets the following requirements: 77 | + Implement `TestGithubOrgClient.test_public_repos` to unit-test `GithubOrgClient.public_repos`. 78 | + Use `@patch` as a decorator to mock `get_json` and make it return a payload of your choice. 79 | + Use `patch` as a context manager to mock `GithubOrgClient._public_repos_url` and return a value of your choice. 80 | + Test that the list of repos is what you expect from the chosen payload. 81 | + Test that the mocked property and the mocked `get_json` was called once. 82 | 83 | + [x] 7. **Parameterize**
[test_client.py](test_client.py) contains a python module that meets the following requirements: 84 | + Implement `TestGithubOrgClient.test_has_license` to unit-test `GithubOrgClient.has_license`. 85 | + Parametrize the test with the following inputs: 86 | ``` 87 | repo={"license": {"key": "bsd-3-clause"}}, license_key="bsd-3-clause" 88 | repo={"license": {"key": "bsl-1.0"}}, license_key="bsd-3-clause" 89 | ``` 90 | + You should also parameterize the expected returned value. 91 | 92 | + [x] 8. **Integration test: fixtures**
[test_client.py](test_client.py) contains a python module that meets the following requirements: 93 | + We want to test the `GithubOrgClient.public_repos` method in an integration test. That means that we will only mock code that sends external requests. 94 | + Create the `TestIntegrationGithubOrgClient(unittest.TestCase)` class and implement the `setUpClass` and `tearDownClass` which are part of the `unittest.TestCase` API. 95 | + Use `@parameterized_class` to decorate the class and parameterize it with fixtures found in [fixtures.py](fixtures.py). The file contains the following fixtures: 96 | ``` 97 | org_payload, repos_payload, expected_repos, apache2_repos 98 | ``` 99 | + The `setupClass` should mock `requests.get` to return example payloads found in the fixtures. 100 | + Use `patch` to start a patcher named `get_patcher`, and use `side_effect` to make sure the mock of `requests.get(url).json()` returns the correct fixtures for the various values of `url` that you anticipate to receive. 101 | + Implement the `tearDownClass` class method to stop the patcher. 102 | 103 | + [x] 9. **Integration tests**
[test_client.py](test_client.py) contains a python module that meets the following requirements: 104 | + Implement the `test_public_repos` method to test `GithubOrgClient.public_repos`. 105 | + Make sure that the method returns the expected results based on the fixtures. 106 | + Implement `test_public_repos_with_license` to test the `public_repos` with the argument `license="apache-2.0"` and make sure the result matches the expected value from the fixtures. 107 | -------------------------------------------------------------------------------- /0x03-Unittests_and_integration_tests/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """A github org client 3 | """ 4 | from typing import ( 5 | List, 6 | Dict, 7 | ) 8 | 9 | from utils import ( 10 | get_json, 11 | access_nested_map, 12 | memoize, 13 | ) 14 | 15 | 16 | class GithubOrgClient: 17 | """A Github org client 18 | """ 19 | ORG_URL = "https://api.github.com/orgs/{org}" 20 | 21 | def __init__(self, org_name: str) -> None: 22 | """Init method of GithubOrgClient""" 23 | self._org_name = org_name 24 | 25 | @memoize 26 | def org(self) -> Dict: 27 | """Memoize org""" 28 | return get_json(self.ORG_URL.format(org=self._org_name)) 29 | 30 | @property 31 | def _public_repos_url(self) -> str: 32 | """Public repos URL""" 33 | return self.org["repos_url"] 34 | 35 | @memoize 36 | def repos_payload(self) -> Dict: 37 | """Memoize repos payload""" 38 | return get_json(self._public_repos_url) 39 | 40 | def public_repos(self, license: str = None) -> List[str]: 41 | """Public repos""" 42 | json_payload = self.repos_payload 43 | public_repos = [ 44 | repo["name"] for repo in json_payload 45 | if license is None or self.has_license(repo, license) 46 | ] 47 | 48 | return public_repos 49 | 50 | @staticmethod 51 | def has_license(repo: Dict[str, Dict], license_key: str) -> bool: 52 | """Static: has_license""" 53 | assert license_key is not None, "license_key cannot be None" 54 | try: 55 | has_license = access_nested_map( 56 | repo, 57 | ("license", "key"), 58 | ) == license_key 59 | except KeyError: 60 | return False 61 | return has_license 62 | -------------------------------------------------------------------------------- /0x03-Unittests_and_integration_tests/fixtures.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | TEST_PAYLOAD = [ 4 | ( 5 | {"repos_url": "https://api.github.com/orgs/google/repos"}, 6 | [ 7 | { 8 | "id": 7697149, 9 | "node_id": "MDEwOlJlcG9zaXRvcnk3Njk3MTQ5", 10 | "name": "episodes.dart", 11 | "full_name": "google/episodes.dart", 12 | "private": False, 13 | "owner": { 14 | "login": "google", 15 | "id": 1342004, 16 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=", 17 | "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4", 18 | "gravatar_id": "", 19 | "url": "https://api.github.com/users/google", 20 | "html_url": "https://github.com/google", 21 | "followers_url": "https://api.github.com/users/google/followers", 22 | "following_url": "https://api.github.com/users/google/following{/other_user}", 23 | "gists_url": "https://api.github.com/users/google/gists{/gist_id}", 24 | "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}", 25 | "subscriptions_url": "https://api.github.com/users/google/subscriptions", 26 | "organizations_url": "https://api.github.com/users/google/orgs", 27 | "repos_url": "https://api.github.com/users/google/repos", 28 | "events_url": "https://api.github.com/users/google/events{/privacy}", 29 | "received_events_url": "https://api.github.com/users/google/received_events", 30 | "type": "Organization", 31 | "site_admin": False 32 | }, 33 | "html_url": "https://github.com/google/episodes.dart", 34 | "description": "A framework for timing performance of web apps.", 35 | "fork": False, 36 | "url": "https://api.github.com/repos/google/episodes.dart", 37 | "forks_url": "https://api.github.com/repos/google/episodes.dart/forks", 38 | "keys_url": "https://api.github.com/repos/google/episodes.dart/keys{/key_id}", 39 | "collaborators_url": "https://api.github.com/repos/google/episodes.dart/collaborators{/collaborator}", 40 | "teams_url": "https://api.github.com/repos/google/episodes.dart/teams", 41 | "hooks_url": "https://api.github.com/repos/google/episodes.dart/hooks", 42 | "issue_events_url": "https://api.github.com/repos/google/episodes.dart/issues/events{/number}", 43 | "events_url": "https://api.github.com/repos/google/episodes.dart/events", 44 | "assignees_url": "https://api.github.com/repos/google/episodes.dart/assignees{/user}", 45 | "branches_url": "https://api.github.com/repos/google/episodes.dart/branches{/branch}", 46 | "tags_url": "https://api.github.com/repos/google/episodes.dart/tags", 47 | "blobs_url": "https://api.github.com/repos/google/episodes.dart/git/blobs{/sha}", 48 | "git_tags_url": "https://api.github.com/repos/google/episodes.dart/git/tags{/sha}", 49 | "git_refs_url": "https://api.github.com/repos/google/episodes.dart/git/refs{/sha}", 50 | "trees_url": "https://api.github.com/repos/google/episodes.dart/git/trees{/sha}", 51 | "statuses_url": "https://api.github.com/repos/google/episodes.dart/statuses/{sha}", 52 | "languages_url": "https://api.github.com/repos/google/episodes.dart/languages", 53 | "stargazers_url": "https://api.github.com/repos/google/episodes.dart/stargazers", 54 | "contributors_url": "https://api.github.com/repos/google/episodes.dart/contributors", 55 | "subscribers_url": "https://api.github.com/repos/google/episodes.dart/subscribers", 56 | "subscription_url": "https://api.github.com/repos/google/episodes.dart/subscription", 57 | "commits_url": "https://api.github.com/repos/google/episodes.dart/commits{/sha}", 58 | "git_commits_url": "https://api.github.com/repos/google/episodes.dart/git/commits{/sha}", 59 | "comments_url": "https://api.github.com/repos/google/episodes.dart/comments{/number}", 60 | "issue_comment_url": "https://api.github.com/repos/google/episodes.dart/issues/comments{/number}", 61 | "contents_url": "https://api.github.com/repos/google/episodes.dart/contents/{+path}", 62 | "compare_url": "https://api.github.com/repos/google/episodes.dart/compare/{base}...{head}", 63 | "merges_url": "https://api.github.com/repos/google/episodes.dart/merges", 64 | "archive_url": "https://api.github.com/repos/google/episodes.dart/{archive_format}{/ref}", 65 | "downloads_url": "https://api.github.com/repos/google/episodes.dart/downloads", 66 | "issues_url": "https://api.github.com/repos/google/episodes.dart/issues{/number}", 67 | "pulls_url": "https://api.github.com/repos/google/episodes.dart/pulls{/number}", 68 | "milestones_url": "https://api.github.com/repos/google/episodes.dart/milestones{/number}", 69 | "notifications_url": "https://api.github.com/repos/google/episodes.dart/notifications{?since,all,participating}", 70 | "labels_url": "https://api.github.com/repos/google/episodes.dart/labels{/name}", 71 | "releases_url": "https://api.github.com/repos/google/episodes.dart/releases{/id}", 72 | "deployments_url": "https://api.github.com/repos/google/episodes.dart/deployments", 73 | "created_at": "2013-01-19T00:31:37Z", 74 | "updated_at": "2019-09-23T11:53:58Z", 75 | "pushed_at": "2014-10-09T21:39:33Z", 76 | "git_url": "git://github.com/google/episodes.dart.git", 77 | "ssh_url": "git@github.com:google/episodes.dart.git", 78 | "clone_url": "https://github.com/google/episodes.dart.git", 79 | "svn_url": "https://github.com/google/episodes.dart", 80 | "homepage": None, 81 | "size": 191, 82 | "stargazers_count": 12, 83 | "watchers_count": 12, 84 | "language": "Dart", 85 | "has_issues": True, 86 | "has_projects": True, 87 | "has_downloads": True, 88 | "has_wiki": True, 89 | "has_pages": False, 90 | "forks_count": 22, 91 | "mirror_url": None, 92 | "archived": False, 93 | "disabled": False, 94 | "open_issues_count": 0, 95 | "license": { 96 | "key": "bsd-3-clause", 97 | "name": "BSD 3-Clause \"New\" or \"Revised\" License", 98 | "spdx_id": "BSD-3-Clause", 99 | "url": "https://api.github.com/licenses/bsd-3-clause", 100 | "node_id": "MDc6TGljZW5zZTU=" 101 | }, 102 | "forks": 22, 103 | "open_issues": 0, 104 | "watchers": 12, 105 | "default_branch": "master", 106 | "permissions": { 107 | "admin": False, 108 | "push": False, 109 | "pull": True 110 | } 111 | }, 112 | { 113 | "id": 7776515, 114 | "node_id": "MDEwOlJlcG9zaXRvcnk3Nzc2NTE1", 115 | "name": "cpp-netlib", 116 | "full_name": "google/cpp-netlib", 117 | "private": False, 118 | "owner": { 119 | "login": "google", 120 | "id": 1342004, 121 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=", 122 | "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4", 123 | "gravatar_id": "", 124 | "url": "https://api.github.com/users/google", 125 | "html_url": "https://github.com/google", 126 | "followers_url": "https://api.github.com/users/google/followers", 127 | "following_url": "https://api.github.com/users/google/following{/other_user}", 128 | "gists_url": "https://api.github.com/users/google/gists{/gist_id}", 129 | "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}", 130 | "subscriptions_url": "https://api.github.com/users/google/subscriptions", 131 | "organizations_url": "https://api.github.com/users/google/orgs", 132 | "repos_url": "https://api.github.com/users/google/repos", 133 | "events_url": "https://api.github.com/users/google/events{/privacy}", 134 | "received_events_url": "https://api.github.com/users/google/received_events", 135 | "type": "Organization", 136 | "site_admin": False 137 | }, 138 | "html_url": "https://github.com/google/cpp-netlib", 139 | "description": "The C++ Network Library Project -- header-only, cross-platform, standards compliant networking library.", 140 | "fork": True, 141 | "url": "https://api.github.com/repos/google/cpp-netlib", 142 | "forks_url": "https://api.github.com/repos/google/cpp-netlib/forks", 143 | "keys_url": "https://api.github.com/repos/google/cpp-netlib/keys{/key_id}", 144 | "collaborators_url": "https://api.github.com/repos/google/cpp-netlib/collaborators{/collaborator}", 145 | "teams_url": "https://api.github.com/repos/google/cpp-netlib/teams", 146 | "hooks_url": "https://api.github.com/repos/google/cpp-netlib/hooks", 147 | "issue_events_url": "https://api.github.com/repos/google/cpp-netlib/issues/events{/number}", 148 | "events_url": "https://api.github.com/repos/google/cpp-netlib/events", 149 | "assignees_url": "https://api.github.com/repos/google/cpp-netlib/assignees{/user}", 150 | "branches_url": "https://api.github.com/repos/google/cpp-netlib/branches{/branch}", 151 | "tags_url": "https://api.github.com/repos/google/cpp-netlib/tags", 152 | "blobs_url": "https://api.github.com/repos/google/cpp-netlib/git/blobs{/sha}", 153 | "git_tags_url": "https://api.github.com/repos/google/cpp-netlib/git/tags{/sha}", 154 | "git_refs_url": "https://api.github.com/repos/google/cpp-netlib/git/refs{/sha}", 155 | "trees_url": "https://api.github.com/repos/google/cpp-netlib/git/trees{/sha}", 156 | "statuses_url": "https://api.github.com/repos/google/cpp-netlib/statuses/{sha}", 157 | "languages_url": "https://api.github.com/repos/google/cpp-netlib/languages", 158 | "stargazers_url": "https://api.github.com/repos/google/cpp-netlib/stargazers", 159 | "contributors_url": "https://api.github.com/repos/google/cpp-netlib/contributors", 160 | "subscribers_url": "https://api.github.com/repos/google/cpp-netlib/subscribers", 161 | "subscription_url": "https://api.github.com/repos/google/cpp-netlib/subscription", 162 | "commits_url": "https://api.github.com/repos/google/cpp-netlib/commits{/sha}", 163 | "git_commits_url": "https://api.github.com/repos/google/cpp-netlib/git/commits{/sha}", 164 | "comments_url": "https://api.github.com/repos/google/cpp-netlib/comments{/number}", 165 | "issue_comment_url": "https://api.github.com/repos/google/cpp-netlib/issues/comments{/number}", 166 | "contents_url": "https://api.github.com/repos/google/cpp-netlib/contents/{+path}", 167 | "compare_url": "https://api.github.com/repos/google/cpp-netlib/compare/{base}...{head}", 168 | "merges_url": "https://api.github.com/repos/google/cpp-netlib/merges", 169 | "archive_url": "https://api.github.com/repos/google/cpp-netlib/{archive_format}{/ref}", 170 | "downloads_url": "https://api.github.com/repos/google/cpp-netlib/downloads", 171 | "issues_url": "https://api.github.com/repos/google/cpp-netlib/issues{/number}", 172 | "pulls_url": "https://api.github.com/repos/google/cpp-netlib/pulls{/number}", 173 | "milestones_url": "https://api.github.com/repos/google/cpp-netlib/milestones{/number}", 174 | "notifications_url": "https://api.github.com/repos/google/cpp-netlib/notifications{?since,all,participating}", 175 | "labels_url": "https://api.github.com/repos/google/cpp-netlib/labels{/name}", 176 | "releases_url": "https://api.github.com/repos/google/cpp-netlib/releases{/id}", 177 | "deployments_url": "https://api.github.com/repos/google/cpp-netlib/deployments", 178 | "created_at": "2013-01-23T14:45:32Z", 179 | "updated_at": "2019-11-15T02:26:31Z", 180 | "pushed_at": "2018-12-05T17:42:29Z", 181 | "git_url": "git://github.com/google/cpp-netlib.git", 182 | "ssh_url": "git@github.com:google/cpp-netlib.git", 183 | "clone_url": "https://github.com/google/cpp-netlib.git", 184 | "svn_url": "https://github.com/google/cpp-netlib", 185 | "homepage": "http://cpp-netlib.github.com/", 186 | "size": 8937, 187 | "stargazers_count": 292, 188 | "watchers_count": 292, 189 | "language": "C++", 190 | "has_issues": False, 191 | "has_projects": True, 192 | "has_downloads": True, 193 | "has_wiki": True, 194 | "has_pages": False, 195 | "forks_count": 59, 196 | "mirror_url": None, 197 | "archived": False, 198 | "disabled": False, 199 | "open_issues_count": 0, 200 | "license": { 201 | "key": "bsl-1.0", 202 | "name": "Boost Software License 1.0", 203 | "spdx_id": "BSL-1.0", 204 | "url": "https://api.github.com/licenses/bsl-1.0", 205 | "node_id": "MDc6TGljZW5zZTI4" 206 | }, 207 | "forks": 59, 208 | "open_issues": 0, 209 | "watchers": 292, 210 | "default_branch": "master", 211 | "permissions": { 212 | "admin": False, 213 | "push": False, 214 | "pull": True 215 | } 216 | }, 217 | { 218 | "id": 7968417, 219 | "node_id": "MDEwOlJlcG9zaXRvcnk3OTY4NDE3", 220 | "name": "dagger", 221 | "full_name": "google/dagger", 222 | "private": False, 223 | "owner": { 224 | "login": "google", 225 | "id": 1342004, 226 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=", 227 | "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4", 228 | "gravatar_id": "", 229 | "url": "https://api.github.com/users/google", 230 | "html_url": "https://github.com/google", 231 | "followers_url": "https://api.github.com/users/google/followers", 232 | "following_url": "https://api.github.com/users/google/following{/other_user}", 233 | "gists_url": "https://api.github.com/users/google/gists{/gist_id}", 234 | "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}", 235 | "subscriptions_url": "https://api.github.com/users/google/subscriptions", 236 | "organizations_url": "https://api.github.com/users/google/orgs", 237 | "repos_url": "https://api.github.com/users/google/repos", 238 | "events_url": "https://api.github.com/users/google/events{/privacy}", 239 | "received_events_url": "https://api.github.com/users/google/received_events", 240 | "type": "Organization", 241 | "site_admin": False 242 | }, 243 | "html_url": "https://github.com/google/dagger", 244 | "description": "A fast dependency injector for Android and Java.", 245 | "fork": True, 246 | "url": "https://api.github.com/repos/google/dagger", 247 | "forks_url": "https://api.github.com/repos/google/dagger/forks", 248 | "keys_url": "https://api.github.com/repos/google/dagger/keys{/key_id}", 249 | "collaborators_url": "https://api.github.com/repos/google/dagger/collaborators{/collaborator}", 250 | "teams_url": "https://api.github.com/repos/google/dagger/teams", 251 | "hooks_url": "https://api.github.com/repos/google/dagger/hooks", 252 | "issue_events_url": "https://api.github.com/repos/google/dagger/issues/events{/number}", 253 | "events_url": "https://api.github.com/repos/google/dagger/events", 254 | "assignees_url": "https://api.github.com/repos/google/dagger/assignees{/user}", 255 | "branches_url": "https://api.github.com/repos/google/dagger/branches{/branch}", 256 | "tags_url": "https://api.github.com/repos/google/dagger/tags", 257 | "blobs_url": "https://api.github.com/repos/google/dagger/git/blobs{/sha}", 258 | "git_tags_url": "https://api.github.com/repos/google/dagger/git/tags{/sha}", 259 | "git_refs_url": "https://api.github.com/repos/google/dagger/git/refs{/sha}", 260 | "trees_url": "https://api.github.com/repos/google/dagger/git/trees{/sha}", 261 | "statuses_url": "https://api.github.com/repos/google/dagger/statuses/{sha}", 262 | "languages_url": "https://api.github.com/repos/google/dagger/languages", 263 | "stargazers_url": "https://api.github.com/repos/google/dagger/stargazers", 264 | "contributors_url": "https://api.github.com/repos/google/dagger/contributors", 265 | "subscribers_url": "https://api.github.com/repos/google/dagger/subscribers", 266 | "subscription_url": "https://api.github.com/repos/google/dagger/subscription", 267 | "commits_url": "https://api.github.com/repos/google/dagger/commits{/sha}", 268 | "git_commits_url": "https://api.github.com/repos/google/dagger/git/commits{/sha}", 269 | "comments_url": "https://api.github.com/repos/google/dagger/comments{/number}", 270 | "issue_comment_url": "https://api.github.com/repos/google/dagger/issues/comments{/number}", 271 | "contents_url": "https://api.github.com/repos/google/dagger/contents/{+path}", 272 | "compare_url": "https://api.github.com/repos/google/dagger/compare/{base}...{head}", 273 | "merges_url": "https://api.github.com/repos/google/dagger/merges", 274 | "archive_url": "https://api.github.com/repos/google/dagger/{archive_format}{/ref}", 275 | "downloads_url": "https://api.github.com/repos/google/dagger/downloads", 276 | "issues_url": "https://api.github.com/repos/google/dagger/issues{/number}", 277 | "pulls_url": "https://api.github.com/repos/google/dagger/pulls{/number}", 278 | "milestones_url": "https://api.github.com/repos/google/dagger/milestones{/number}", 279 | "notifications_url": "https://api.github.com/repos/google/dagger/notifications{?since,all,participating}", 280 | "labels_url": "https://api.github.com/repos/google/dagger/labels{/name}", 281 | "releases_url": "https://api.github.com/repos/google/dagger/releases{/id}", 282 | "deployments_url": "https://api.github.com/repos/google/dagger/deployments", 283 | "created_at": "2013-02-01T23:14:14Z", 284 | "updated_at": "2019-12-03T12:39:55Z", 285 | "pushed_at": "2019-11-27T21:20:38Z", 286 | "git_url": "git://github.com/google/dagger.git", 287 | "ssh_url": "git@github.com:google/dagger.git", 288 | "clone_url": "https://github.com/google/dagger.git", 289 | "svn_url": "https://github.com/google/dagger", 290 | "homepage": "https://dagger.dev", 291 | "size": 59129, 292 | "stargazers_count": 14492, 293 | "watchers_count": 14492, 294 | "language": "Java", 295 | "has_issues": True, 296 | "has_projects": True, 297 | "has_downloads": True, 298 | "has_wiki": True, 299 | "has_pages": True, 300 | "forks_count": 1741, 301 | "mirror_url": None, 302 | "archived": False, 303 | "disabled": False, 304 | "open_issues_count": 148, 305 | "license": { 306 | "key": "apache-2.0", 307 | "name": "Apache License 2.0", 308 | "spdx_id": "Apache-2.0", 309 | "url": "https://api.github.com/licenses/apache-2.0", 310 | "node_id": "MDc6TGljZW5zZTI=" 311 | }, 312 | "forks": 1741, 313 | "open_issues": 148, 314 | "watchers": 14492, 315 | "default_branch": "master", 316 | "permissions": { 317 | "admin": False, 318 | "push": False, 319 | "pull": True 320 | } 321 | }, 322 | { 323 | "id": 8165161, 324 | "node_id": "MDEwOlJlcG9zaXRvcnk4MTY1MTYx", 325 | "name": "ios-webkit-debug-proxy", 326 | "full_name": "google/ios-webkit-debug-proxy", 327 | "private": False, 328 | "owner": { 329 | "login": "google", 330 | "id": 1342004, 331 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=", 332 | "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4", 333 | "gravatar_id": "", 334 | "url": "https://api.github.com/users/google", 335 | "html_url": "https://github.com/google", 336 | "followers_url": "https://api.github.com/users/google/followers", 337 | "following_url": "https://api.github.com/users/google/following{/other_user}", 338 | "gists_url": "https://api.github.com/users/google/gists{/gist_id}", 339 | "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}", 340 | "subscriptions_url": "https://api.github.com/users/google/subscriptions", 341 | "organizations_url": "https://api.github.com/users/google/orgs", 342 | "repos_url": "https://api.github.com/users/google/repos", 343 | "events_url": "https://api.github.com/users/google/events{/privacy}", 344 | "received_events_url": "https://api.github.com/users/google/received_events", 345 | "type": "Organization", 346 | "site_admin": False 347 | }, 348 | "html_url": "https://github.com/google/ios-webkit-debug-proxy", 349 | "description": "A DevTools proxy (Chrome Remote Debugging Protocol) for iOS devices (Safari Remote Web Inspector).", 350 | "fork": False, 351 | "url": "https://api.github.com/repos/google/ios-webkit-debug-proxy", 352 | "forks_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/forks", 353 | "keys_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/keys{/key_id}", 354 | "collaborators_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/collaborators{/collaborator}", 355 | "teams_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/teams", 356 | "hooks_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/hooks", 357 | "issue_events_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/issues/events{/number}", 358 | "events_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/events", 359 | "assignees_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/assignees{/user}", 360 | "branches_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/branches{/branch}", 361 | "tags_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/tags", 362 | "blobs_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/git/blobs{/sha}", 363 | "git_tags_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/git/tags{/sha}", 364 | "git_refs_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/git/refs{/sha}", 365 | "trees_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/git/trees{/sha}", 366 | "statuses_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/statuses/{sha}", 367 | "languages_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/languages", 368 | "stargazers_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/stargazers", 369 | "contributors_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/contributors", 370 | "subscribers_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/subscribers", 371 | "subscription_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/subscription", 372 | "commits_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/commits{/sha}", 373 | "git_commits_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/git/commits{/sha}", 374 | "comments_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/comments{/number}", 375 | "issue_comment_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/issues/comments{/number}", 376 | "contents_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/contents/{+path}", 377 | "compare_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/compare/{base}...{head}", 378 | "merges_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/merges", 379 | "archive_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/{archive_format}{/ref}", 380 | "downloads_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/downloads", 381 | "issues_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/issues{/number}", 382 | "pulls_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/pulls{/number}", 383 | "milestones_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/milestones{/number}", 384 | "notifications_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/notifications{?since,all,participating}", 385 | "labels_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/labels{/name}", 386 | "releases_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/releases{/id}", 387 | "deployments_url": "https://api.github.com/repos/google/ios-webkit-debug-proxy/deployments", 388 | "created_at": "2013-02-12T19:08:19Z", 389 | "updated_at": "2019-12-04T02:06:43Z", 390 | "pushed_at": "2019-11-24T07:02:13Z", 391 | "git_url": "git://github.com/google/ios-webkit-debug-proxy.git", 392 | "ssh_url": "git@github.com:google/ios-webkit-debug-proxy.git", 393 | "clone_url": "https://github.com/google/ios-webkit-debug-proxy.git", 394 | "svn_url": "https://github.com/google/ios-webkit-debug-proxy", 395 | "homepage": "", 396 | "size": 680, 397 | "stargazers_count": 4630, 398 | "watchers_count": 4630, 399 | "language": "C", 400 | "has_issues": True, 401 | "has_projects": True, 402 | "has_downloads": True, 403 | "has_wiki": False, 404 | "has_pages": False, 405 | "forks_count": 395, 406 | "mirror_url": None, 407 | "archived": False, 408 | "disabled": False, 409 | "open_issues_count": 24, 410 | "license": { 411 | "key": "other", 412 | "name": "Other", 413 | "spdx_id": "NOASSERTION", 414 | "url": None, 415 | "node_id": "MDc6TGljZW5zZTA=" 416 | }, 417 | "forks": 395, 418 | "open_issues": 24, 419 | "watchers": 4630, 420 | "default_branch": "master", 421 | "permissions": { 422 | "admin": False, 423 | "push": False, 424 | "pull": True 425 | } 426 | }, 427 | { 428 | "id": 8459994, 429 | "node_id": "MDEwOlJlcG9zaXRvcnk4NDU5OTk0", 430 | "name": "google.github.io", 431 | "full_name": "google/google.github.io", 432 | "private": False, 433 | "owner": { 434 | "login": "google", 435 | "id": 1342004, 436 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=", 437 | "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4", 438 | "gravatar_id": "", 439 | "url": "https://api.github.com/users/google", 440 | "html_url": "https://github.com/google", 441 | "followers_url": "https://api.github.com/users/google/followers", 442 | "following_url": "https://api.github.com/users/google/following{/other_user}", 443 | "gists_url": "https://api.github.com/users/google/gists{/gist_id}", 444 | "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}", 445 | "subscriptions_url": "https://api.github.com/users/google/subscriptions", 446 | "organizations_url": "https://api.github.com/users/google/orgs", 447 | "repos_url": "https://api.github.com/users/google/repos", 448 | "events_url": "https://api.github.com/users/google/events{/privacy}", 449 | "received_events_url": "https://api.github.com/users/google/received_events", 450 | "type": "Organization", 451 | "site_admin": False 452 | }, 453 | "html_url": "https://github.com/google/google.github.io", 454 | "description": None, 455 | "fork": False, 456 | "url": "https://api.github.com/repos/google/google.github.io", 457 | "forks_url": "https://api.github.com/repos/google/google.github.io/forks", 458 | "keys_url": "https://api.github.com/repos/google/google.github.io/keys{/key_id}", 459 | "collaborators_url": "https://api.github.com/repos/google/google.github.io/collaborators{/collaborator}", 460 | "teams_url": "https://api.github.com/repos/google/google.github.io/teams", 461 | "hooks_url": "https://api.github.com/repos/google/google.github.io/hooks", 462 | "issue_events_url": "https://api.github.com/repos/google/google.github.io/issues/events{/number}", 463 | "events_url": "https://api.github.com/repos/google/google.github.io/events", 464 | "assignees_url": "https://api.github.com/repos/google/google.github.io/assignees{/user}", 465 | "branches_url": "https://api.github.com/repos/google/google.github.io/branches{/branch}", 466 | "tags_url": "https://api.github.com/repos/google/google.github.io/tags", 467 | "blobs_url": "https://api.github.com/repos/google/google.github.io/git/blobs{/sha}", 468 | "git_tags_url": "https://api.github.com/repos/google/google.github.io/git/tags{/sha}", 469 | "git_refs_url": "https://api.github.com/repos/google/google.github.io/git/refs{/sha}", 470 | "trees_url": "https://api.github.com/repos/google/google.github.io/git/trees{/sha}", 471 | "statuses_url": "https://api.github.com/repos/google/google.github.io/statuses/{sha}", 472 | "languages_url": "https://api.github.com/repos/google/google.github.io/languages", 473 | "stargazers_url": "https://api.github.com/repos/google/google.github.io/stargazers", 474 | "contributors_url": "https://api.github.com/repos/google/google.github.io/contributors", 475 | "subscribers_url": "https://api.github.com/repos/google/google.github.io/subscribers", 476 | "subscription_url": "https://api.github.com/repos/google/google.github.io/subscription", 477 | "commits_url": "https://api.github.com/repos/google/google.github.io/commits{/sha}", 478 | "git_commits_url": "https://api.github.com/repos/google/google.github.io/git/commits{/sha}", 479 | "comments_url": "https://api.github.com/repos/google/google.github.io/comments{/number}", 480 | "issue_comment_url": "https://api.github.com/repos/google/google.github.io/issues/comments{/number}", 481 | "contents_url": "https://api.github.com/repos/google/google.github.io/contents/{+path}", 482 | "compare_url": "https://api.github.com/repos/google/google.github.io/compare/{base}...{head}", 483 | "merges_url": "https://api.github.com/repos/google/google.github.io/merges", 484 | "archive_url": "https://api.github.com/repos/google/google.github.io/{archive_format}{/ref}", 485 | "downloads_url": "https://api.github.com/repos/google/google.github.io/downloads", 486 | "issues_url": "https://api.github.com/repos/google/google.github.io/issues{/number}", 487 | "pulls_url": "https://api.github.com/repos/google/google.github.io/pulls{/number}", 488 | "milestones_url": "https://api.github.com/repos/google/google.github.io/milestones{/number}", 489 | "notifications_url": "https://api.github.com/repos/google/google.github.io/notifications{?since,all,participating}", 490 | "labels_url": "https://api.github.com/repos/google/google.github.io/labels{/name}", 491 | "releases_url": "https://api.github.com/repos/google/google.github.io/releases{/id}", 492 | "deployments_url": "https://api.github.com/repos/google/google.github.io/deployments", 493 | "created_at": "2013-02-27T16:21:19Z", 494 | "updated_at": "2019-12-03T01:38:02Z", 495 | "pushed_at": "2019-12-03T01:37:58Z", 496 | "git_url": "git://github.com/google/google.github.io.git", 497 | "ssh_url": "git@github.com:google/google.github.io.git", 498 | "clone_url": "https://github.com/google/google.github.io.git", 499 | "svn_url": "https://github.com/google/google.github.io", 500 | "homepage": None, 501 | "size": 8, 502 | "stargazers_count": 38, 503 | "watchers_count": 38, 504 | "language": "HTML", 505 | "has_issues": False, 506 | "has_projects": True, 507 | "has_downloads": True, 508 | "has_wiki": False, 509 | "has_pages": True, 510 | "forks_count": 44, 511 | "mirror_url": None, 512 | "archived": False, 513 | "disabled": False, 514 | "open_issues_count": 0, 515 | "license": None, 516 | "forks": 44, 517 | "open_issues": 0, 518 | "watchers": 38, 519 | "default_branch": "master", 520 | "permissions": { 521 | "admin": False, 522 | "push": False, 523 | "pull": True 524 | } 525 | }, 526 | { 527 | "id": 8566972, 528 | "node_id": "MDEwOlJlcG9zaXRvcnk4NTY2OTcy", 529 | "name": "kratu", 530 | "full_name": "google/kratu", 531 | "private": False, 532 | "owner": { 533 | "login": "google", 534 | "id": 1342004, 535 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=", 536 | "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4", 537 | "gravatar_id": "", 538 | "url": "https://api.github.com/users/google", 539 | "html_url": "https://github.com/google", 540 | "followers_url": "https://api.github.com/users/google/followers", 541 | "following_url": "https://api.github.com/users/google/following{/other_user}", 542 | "gists_url": "https://api.github.com/users/google/gists{/gist_id}", 543 | "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}", 544 | "subscriptions_url": "https://api.github.com/users/google/subscriptions", 545 | "organizations_url": "https://api.github.com/users/google/orgs", 546 | "repos_url": "https://api.github.com/users/google/repos", 547 | "events_url": "https://api.github.com/users/google/events{/privacy}", 548 | "received_events_url": "https://api.github.com/users/google/received_events", 549 | "type": "Organization", 550 | "site_admin": False 551 | }, 552 | "html_url": "https://github.com/google/kratu", 553 | "description": None, 554 | "fork": False, 555 | "url": "https://api.github.com/repos/google/kratu", 556 | "forks_url": "https://api.github.com/repos/google/kratu/forks", 557 | "keys_url": "https://api.github.com/repos/google/kratu/keys{/key_id}", 558 | "collaborators_url": "https://api.github.com/repos/google/kratu/collaborators{/collaborator}", 559 | "teams_url": "https://api.github.com/repos/google/kratu/teams", 560 | "hooks_url": "https://api.github.com/repos/google/kratu/hooks", 561 | "issue_events_url": "https://api.github.com/repos/google/kratu/issues/events{/number}", 562 | "events_url": "https://api.github.com/repos/google/kratu/events", 563 | "assignees_url": "https://api.github.com/repos/google/kratu/assignees{/user}", 564 | "branches_url": "https://api.github.com/repos/google/kratu/branches{/branch}", 565 | "tags_url": "https://api.github.com/repos/google/kratu/tags", 566 | "blobs_url": "https://api.github.com/repos/google/kratu/git/blobs{/sha}", 567 | "git_tags_url": "https://api.github.com/repos/google/kratu/git/tags{/sha}", 568 | "git_refs_url": "https://api.github.com/repos/google/kratu/git/refs{/sha}", 569 | "trees_url": "https://api.github.com/repos/google/kratu/git/trees{/sha}", 570 | "statuses_url": "https://api.github.com/repos/google/kratu/statuses/{sha}", 571 | "languages_url": "https://api.github.com/repos/google/kratu/languages", 572 | "stargazers_url": "https://api.github.com/repos/google/kratu/stargazers", 573 | "contributors_url": "https://api.github.com/repos/google/kratu/contributors", 574 | "subscribers_url": "https://api.github.com/repos/google/kratu/subscribers", 575 | "subscription_url": "https://api.github.com/repos/google/kratu/subscription", 576 | "commits_url": "https://api.github.com/repos/google/kratu/commits{/sha}", 577 | "git_commits_url": "https://api.github.com/repos/google/kratu/git/commits{/sha}", 578 | "comments_url": "https://api.github.com/repos/google/kratu/comments{/number}", 579 | "issue_comment_url": "https://api.github.com/repos/google/kratu/issues/comments{/number}", 580 | "contents_url": "https://api.github.com/repos/google/kratu/contents/{+path}", 581 | "compare_url": "https://api.github.com/repos/google/kratu/compare/{base}...{head}", 582 | "merges_url": "https://api.github.com/repos/google/kratu/merges", 583 | "archive_url": "https://api.github.com/repos/google/kratu/{archive_format}{/ref}", 584 | "downloads_url": "https://api.github.com/repos/google/kratu/downloads", 585 | "issues_url": "https://api.github.com/repos/google/kratu/issues{/number}", 586 | "pulls_url": "https://api.github.com/repos/google/kratu/pulls{/number}", 587 | "milestones_url": "https://api.github.com/repos/google/kratu/milestones{/number}", 588 | "notifications_url": "https://api.github.com/repos/google/kratu/notifications{?since,all,participating}", 589 | "labels_url": "https://api.github.com/repos/google/kratu/labels{/name}", 590 | "releases_url": "https://api.github.com/repos/google/kratu/releases{/id}", 591 | "deployments_url": "https://api.github.com/repos/google/kratu/deployments", 592 | "created_at": "2013-03-04T22:52:33Z", 593 | "updated_at": "2019-11-15T22:22:16Z", 594 | "pushed_at": "2017-08-06T05:44:34Z", 595 | "git_url": "git://github.com/google/kratu.git", 596 | "ssh_url": "git@github.com:google/kratu.git", 597 | "clone_url": "https://github.com/google/kratu.git", 598 | "svn_url": "https://github.com/google/kratu", 599 | "homepage": None, 600 | "size": 1777, 601 | "stargazers_count": 280, 602 | "watchers_count": 280, 603 | "language": "JavaScript", 604 | "has_issues": True, 605 | "has_projects": True, 606 | "has_downloads": True, 607 | "has_wiki": True, 608 | "has_pages": True, 609 | "forks_count": 32, 610 | "mirror_url": None, 611 | "archived": False, 612 | "disabled": False, 613 | "open_issues_count": 0, 614 | "license": { 615 | "key": "apache-2.0", 616 | "name": "Apache License 2.0", 617 | "spdx_id": "Apache-2.0", 618 | "url": "https://api.github.com/licenses/apache-2.0", 619 | "node_id": "MDc6TGljZW5zZTI=" 620 | }, 621 | "forks": 32, 622 | "open_issues": 0, 623 | "watchers": 280, 624 | "default_branch": "master", 625 | "permissions": { 626 | "admin": False, 627 | "push": False, 628 | "pull": True 629 | } 630 | }, 631 | { 632 | "id": 8858648, 633 | "node_id": "MDEwOlJlcG9zaXRvcnk4ODU4NjQ4", 634 | "name": "build-debian-cloud", 635 | "full_name": "google/build-debian-cloud", 636 | "private": False, 637 | "owner": { 638 | "login": "google", 639 | "id": 1342004, 640 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=", 641 | "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4", 642 | "gravatar_id": "", 643 | "url": "https://api.github.com/users/google", 644 | "html_url": "https://github.com/google", 645 | "followers_url": "https://api.github.com/users/google/followers", 646 | "following_url": "https://api.github.com/users/google/following{/other_user}", 647 | "gists_url": "https://api.github.com/users/google/gists{/gist_id}", 648 | "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}", 649 | "subscriptions_url": "https://api.github.com/users/google/subscriptions", 650 | "organizations_url": "https://api.github.com/users/google/orgs", 651 | "repos_url": "https://api.github.com/users/google/repos", 652 | "events_url": "https://api.github.com/users/google/events{/privacy}", 653 | "received_events_url": "https://api.github.com/users/google/received_events", 654 | "type": "Organization", 655 | "site_admin": False 656 | }, 657 | "html_url": "https://github.com/google/build-debian-cloud", 658 | "description": "Script to create Debian Squeeze & Wheezy Amazon Machine Images (AMIs) and Google Compute Engine images", 659 | "fork": True, 660 | "url": "https://api.github.com/repos/google/build-debian-cloud", 661 | "forks_url": "https://api.github.com/repos/google/build-debian-cloud/forks", 662 | "keys_url": "https://api.github.com/repos/google/build-debian-cloud/keys{/key_id}", 663 | "collaborators_url": "https://api.github.com/repos/google/build-debian-cloud/collaborators{/collaborator}", 664 | "teams_url": "https://api.github.com/repos/google/build-debian-cloud/teams", 665 | "hooks_url": "https://api.github.com/repos/google/build-debian-cloud/hooks", 666 | "issue_events_url": "https://api.github.com/repos/google/build-debian-cloud/issues/events{/number}", 667 | "events_url": "https://api.github.com/repos/google/build-debian-cloud/events", 668 | "assignees_url": "https://api.github.com/repos/google/build-debian-cloud/assignees{/user}", 669 | "branches_url": "https://api.github.com/repos/google/build-debian-cloud/branches{/branch}", 670 | "tags_url": "https://api.github.com/repos/google/build-debian-cloud/tags", 671 | "blobs_url": "https://api.github.com/repos/google/build-debian-cloud/git/blobs{/sha}", 672 | "git_tags_url": "https://api.github.com/repos/google/build-debian-cloud/git/tags{/sha}", 673 | "git_refs_url": "https://api.github.com/repos/google/build-debian-cloud/git/refs{/sha}", 674 | "trees_url": "https://api.github.com/repos/google/build-debian-cloud/git/trees{/sha}", 675 | "statuses_url": "https://api.github.com/repos/google/build-debian-cloud/statuses/{sha}", 676 | "languages_url": "https://api.github.com/repos/google/build-debian-cloud/languages", 677 | "stargazers_url": "https://api.github.com/repos/google/build-debian-cloud/stargazers", 678 | "contributors_url": "https://api.github.com/repos/google/build-debian-cloud/contributors", 679 | "subscribers_url": "https://api.github.com/repos/google/build-debian-cloud/subscribers", 680 | "subscription_url": "https://api.github.com/repos/google/build-debian-cloud/subscription", 681 | "commits_url": "https://api.github.com/repos/google/build-debian-cloud/commits{/sha}", 682 | "git_commits_url": "https://api.github.com/repos/google/build-debian-cloud/git/commits{/sha}", 683 | "comments_url": "https://api.github.com/repos/google/build-debian-cloud/comments{/number}", 684 | "issue_comment_url": "https://api.github.com/repos/google/build-debian-cloud/issues/comments{/number}", 685 | "contents_url": "https://api.github.com/repos/google/build-debian-cloud/contents/{+path}", 686 | "compare_url": "https://api.github.com/repos/google/build-debian-cloud/compare/{base}...{head}", 687 | "merges_url": "https://api.github.com/repos/google/build-debian-cloud/merges", 688 | "archive_url": "https://api.github.com/repos/google/build-debian-cloud/{archive_format}{/ref}", 689 | "downloads_url": "https://api.github.com/repos/google/build-debian-cloud/downloads", 690 | "issues_url": "https://api.github.com/repos/google/build-debian-cloud/issues{/number}", 691 | "pulls_url": "https://api.github.com/repos/google/build-debian-cloud/pulls{/number}", 692 | "milestones_url": "https://api.github.com/repos/google/build-debian-cloud/milestones{/number}", 693 | "notifications_url": "https://api.github.com/repos/google/build-debian-cloud/notifications{?since,all,participating}", 694 | "labels_url": "https://api.github.com/repos/google/build-debian-cloud/labels{/name}", 695 | "releases_url": "https://api.github.com/repos/google/build-debian-cloud/releases{/id}", 696 | "deployments_url": "https://api.github.com/repos/google/build-debian-cloud/deployments", 697 | "created_at": "2013-03-18T16:32:00Z", 698 | "updated_at": "2019-09-23T11:54:00Z", 699 | "pushed_at": "2014-06-17T18:52:10Z", 700 | "git_url": "git://github.com/google/build-debian-cloud.git", 701 | "ssh_url": "git@github.com:google/build-debian-cloud.git", 702 | "clone_url": "https://github.com/google/build-debian-cloud.git", 703 | "svn_url": "https://github.com/google/build-debian-cloud", 704 | "homepage": "", 705 | "size": 986, 706 | "stargazers_count": 32, 707 | "watchers_count": 32, 708 | "language": "Shell", 709 | "has_issues": False, 710 | "has_projects": True, 711 | "has_downloads": True, 712 | "has_wiki": False, 713 | "has_pages": False, 714 | "forks_count": 22, 715 | "mirror_url": None, 716 | "archived": False, 717 | "disabled": False, 718 | "open_issues_count": 5, 719 | "license": { 720 | "key": "other", 721 | "name": "Other", 722 | "spdx_id": "NOASSERTION", 723 | "url": None, 724 | "node_id": "MDc6TGljZW5zZTA=" 725 | }, 726 | "forks": 22, 727 | "open_issues": 5, 728 | "watchers": 32, 729 | "default_branch": "master", 730 | "permissions": { 731 | "admin": False, 732 | "push": False, 733 | "pull": True 734 | } 735 | }, 736 | { 737 | "id": 9060347, 738 | "node_id": "MDEwOlJlcG9zaXRvcnk5MDYwMzQ3", 739 | "name": "traceur-compiler", 740 | "full_name": "google/traceur-compiler", 741 | "private": False, 742 | "owner": { 743 | "login": "google", 744 | "id": 1342004, 745 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=", 746 | "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4", 747 | "gravatar_id": "", 748 | "url": "https://api.github.com/users/google", 749 | "html_url": "https://github.com/google", 750 | "followers_url": "https://api.github.com/users/google/followers", 751 | "following_url": "https://api.github.com/users/google/following{/other_user}", 752 | "gists_url": "https://api.github.com/users/google/gists{/gist_id}", 753 | "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}", 754 | "subscriptions_url": "https://api.github.com/users/google/subscriptions", 755 | "organizations_url": "https://api.github.com/users/google/orgs", 756 | "repos_url": "https://api.github.com/users/google/repos", 757 | "events_url": "https://api.github.com/users/google/events{/privacy}", 758 | "received_events_url": "https://api.github.com/users/google/received_events", 759 | "type": "Organization", 760 | "site_admin": False 761 | }, 762 | "html_url": "https://github.com/google/traceur-compiler", 763 | "description": "Traceur is a JavaScript.next-to-JavaScript-of-today compiler", 764 | "fork": False, 765 | "url": "https://api.github.com/repos/google/traceur-compiler", 766 | "forks_url": "https://api.github.com/repos/google/traceur-compiler/forks", 767 | "keys_url": "https://api.github.com/repos/google/traceur-compiler/keys{/key_id}", 768 | "collaborators_url": "https://api.github.com/repos/google/traceur-compiler/collaborators{/collaborator}", 769 | "teams_url": "https://api.github.com/repos/google/traceur-compiler/teams", 770 | "hooks_url": "https://api.github.com/repos/google/traceur-compiler/hooks", 771 | "issue_events_url": "https://api.github.com/repos/google/traceur-compiler/issues/events{/number}", 772 | "events_url": "https://api.github.com/repos/google/traceur-compiler/events", 773 | "assignees_url": "https://api.github.com/repos/google/traceur-compiler/assignees{/user}", 774 | "branches_url": "https://api.github.com/repos/google/traceur-compiler/branches{/branch}", 775 | "tags_url": "https://api.github.com/repos/google/traceur-compiler/tags", 776 | "blobs_url": "https://api.github.com/repos/google/traceur-compiler/git/blobs{/sha}", 777 | "git_tags_url": "https://api.github.com/repos/google/traceur-compiler/git/tags{/sha}", 778 | "git_refs_url": "https://api.github.com/repos/google/traceur-compiler/git/refs{/sha}", 779 | "trees_url": "https://api.github.com/repos/google/traceur-compiler/git/trees{/sha}", 780 | "statuses_url": "https://api.github.com/repos/google/traceur-compiler/statuses/{sha}", 781 | "languages_url": "https://api.github.com/repos/google/traceur-compiler/languages", 782 | "stargazers_url": "https://api.github.com/repos/google/traceur-compiler/stargazers", 783 | "contributors_url": "https://api.github.com/repos/google/traceur-compiler/contributors", 784 | "subscribers_url": "https://api.github.com/repos/google/traceur-compiler/subscribers", 785 | "subscription_url": "https://api.github.com/repos/google/traceur-compiler/subscription", 786 | "commits_url": "https://api.github.com/repos/google/traceur-compiler/commits{/sha}", 787 | "git_commits_url": "https://api.github.com/repos/google/traceur-compiler/git/commits{/sha}", 788 | "comments_url": "https://api.github.com/repos/google/traceur-compiler/comments{/number}", 789 | "issue_comment_url": "https://api.github.com/repos/google/traceur-compiler/issues/comments{/number}", 790 | "contents_url": "https://api.github.com/repos/google/traceur-compiler/contents/{+path}", 791 | "compare_url": "https://api.github.com/repos/google/traceur-compiler/compare/{base}...{head}", 792 | "merges_url": "https://api.github.com/repos/google/traceur-compiler/merges", 793 | "archive_url": "https://api.github.com/repos/google/traceur-compiler/{archive_format}{/ref}", 794 | "downloads_url": "https://api.github.com/repos/google/traceur-compiler/downloads", 795 | "issues_url": "https://api.github.com/repos/google/traceur-compiler/issues{/number}", 796 | "pulls_url": "https://api.github.com/repos/google/traceur-compiler/pulls{/number}", 797 | "milestones_url": "https://api.github.com/repos/google/traceur-compiler/milestones{/number}", 798 | "notifications_url": "https://api.github.com/repos/google/traceur-compiler/notifications{?since,all,participating}", 799 | "labels_url": "https://api.github.com/repos/google/traceur-compiler/labels{/name}", 800 | "releases_url": "https://api.github.com/repos/google/traceur-compiler/releases{/id}", 801 | "deployments_url": "https://api.github.com/repos/google/traceur-compiler/deployments", 802 | "created_at": "2013-03-27T18:05:40Z", 803 | "updated_at": "2019-12-02T16:45:54Z", 804 | "pushed_at": "2018-05-28T04:37:54Z", 805 | "git_url": "git://github.com/google/traceur-compiler.git", 806 | "ssh_url": "git@github.com:google/traceur-compiler.git", 807 | "clone_url": "https://github.com/google/traceur-compiler.git", 808 | "svn_url": "https://github.com/google/traceur-compiler", 809 | "homepage": "", 810 | "size": 27487, 811 | "stargazers_count": 8033, 812 | "watchers_count": 8033, 813 | "language": "JavaScript", 814 | "has_issues": True, 815 | "has_projects": True, 816 | "has_downloads": True, 817 | "has_wiki": True, 818 | "has_pages": True, 819 | "forks_count": 604, 820 | "mirror_url": None, 821 | "archived": False, 822 | "disabled": False, 823 | "open_issues_count": 296, 824 | "license": { 825 | "key": "apache-2.0", 826 | "name": "Apache License 2.0", 827 | "spdx_id": "Apache-2.0", 828 | "url": "https://api.github.com/licenses/apache-2.0", 829 | "node_id": "MDc6TGljZW5zZTI=" 830 | }, 831 | "forks": 604, 832 | "open_issues": 296, 833 | "watchers": 8033, 834 | "default_branch": "master", 835 | "permissions": { 836 | "admin": False, 837 | "push": False, 838 | "pull": True 839 | } 840 | }, 841 | { 842 | "id": 9065917, 843 | "node_id": "MDEwOlJlcG9zaXRvcnk5MDY1OTE3", 844 | "name": "firmata.py", 845 | "full_name": "google/firmata.py", 846 | "private": False, 847 | "owner": { 848 | "login": "google", 849 | "id": 1342004, 850 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=", 851 | "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4", 852 | "gravatar_id": "", 853 | "url": "https://api.github.com/users/google", 854 | "html_url": "https://github.com/google", 855 | "followers_url": "https://api.github.com/users/google/followers", 856 | "following_url": "https://api.github.com/users/google/following{/other_user}", 857 | "gists_url": "https://api.github.com/users/google/gists{/gist_id}", 858 | "starred_url": "https://api.github.com/users/google/starred{/owner}{/repo}", 859 | "subscriptions_url": "https://api.github.com/users/google/subscriptions", 860 | "organizations_url": "https://api.github.com/users/google/orgs", 861 | "repos_url": "https://api.github.com/users/google/repos", 862 | "events_url": "https://api.github.com/users/google/events{/privacy}", 863 | "received_events_url": "https://api.github.com/users/google/received_events", 864 | "type": "Organization", 865 | "site_admin": False 866 | }, 867 | "html_url": "https://github.com/google/firmata.py", 868 | "description": None, 869 | "fork": False, 870 | "url": "https://api.github.com/repos/google/firmata.py", 871 | "forks_url": "https://api.github.com/repos/google/firmata.py/forks", 872 | "keys_url": "https://api.github.com/repos/google/firmata.py/keys{/key_id}", 873 | "collaborators_url": "https://api.github.com/repos/google/firmata.py/collaborators{/collaborator}", 874 | "teams_url": "https://api.github.com/repos/google/firmata.py/teams", 875 | "hooks_url": "https://api.github.com/repos/google/firmata.py/hooks", 876 | "issue_events_url": "https://api.github.com/repos/google/firmata.py/issues/events{/number}", 877 | "events_url": "https://api.github.com/repos/google/firmata.py/events", 878 | "assignees_url": "https://api.github.com/repos/google/firmata.py/assignees{/user}", 879 | "branches_url": "https://api.github.com/repos/google/firmata.py/branches{/branch}", 880 | "tags_url": "https://api.github.com/repos/google/firmata.py/tags", 881 | "blobs_url": "https://api.github.com/repos/google/firmata.py/git/blobs{/sha}", 882 | "git_tags_url": "https://api.github.com/repos/google/firmata.py/git/tags{/sha}", 883 | "git_refs_url": "https://api.github.com/repos/google/firmata.py/git/refs{/sha}", 884 | "trees_url": "https://api.github.com/repos/google/firmata.py/git/trees{/sha}", 885 | "statuses_url": "https://api.github.com/repos/google/firmata.py/statuses/{sha}", 886 | "languages_url": "https://api.github.com/repos/google/firmata.py/languages", 887 | "stargazers_url": "https://api.github.com/repos/google/firmata.py/stargazers", 888 | "contributors_url": "https://api.github.com/repos/google/firmata.py/contributors", 889 | "subscribers_url": "https://api.github.com/repos/google/firmata.py/subscribers", 890 | "subscription_url": "https://api.github.com/repos/google/firmata.py/subscription", 891 | "commits_url": "https://api.github.com/repos/google/firmata.py/commits{/sha}", 892 | "git_commits_url": "https://api.github.com/repos/google/firmata.py/git/commits{/sha}", 893 | "comments_url": "https://api.github.com/repos/google/firmata.py/comments{/number}", 894 | "issue_comment_url": "https://api.github.com/repos/google/firmata.py/issues/comments{/number}", 895 | "contents_url": "https://api.github.com/repos/google/firmata.py/contents/{+path}", 896 | "compare_url": "https://api.github.com/repos/google/firmata.py/compare/{base}...{head}", 897 | "merges_url": "https://api.github.com/repos/google/firmata.py/merges", 898 | "archive_url": "https://api.github.com/repos/google/firmata.py/{archive_format}{/ref}", 899 | "downloads_url": "https://api.github.com/repos/google/firmata.py/downloads", 900 | "issues_url": "https://api.github.com/repos/google/firmata.py/issues{/number}", 901 | "pulls_url": "https://api.github.com/repos/google/firmata.py/pulls{/number}", 902 | "milestones_url": "https://api.github.com/repos/google/firmata.py/milestones{/number}", 903 | "notifications_url": "https://api.github.com/repos/google/firmata.py/notifications{?since,all,participating}", 904 | "labels_url": "https://api.github.com/repos/google/firmata.py/labels{/name}", 905 | "releases_url": "https://api.github.com/repos/google/firmata.py/releases{/id}", 906 | "deployments_url": "https://api.github.com/repos/google/firmata.py/deployments", 907 | "created_at": "2013-03-27T23:20:35Z", 908 | "updated_at": "2019-09-23T11:54:02Z", 909 | "pushed_at": "2013-03-27T23:34:35Z", 910 | "git_url": "git://github.com/google/firmata.py.git", 911 | "ssh_url": "git@github.com:google/firmata.py.git", 912 | "clone_url": "https://github.com/google/firmata.py.git", 913 | "svn_url": "https://github.com/google/firmata.py", 914 | "homepage": None, 915 | "size": 160, 916 | "stargazers_count": 15, 917 | "watchers_count": 15, 918 | "language": "Python", 919 | "has_issues": True, 920 | "has_projects": True, 921 | "has_downloads": True, 922 | "has_wiki": True, 923 | "has_pages": False, 924 | "forks_count": 15, 925 | "mirror_url": None, 926 | "archived": False, 927 | "disabled": False, 928 | "open_issues_count": 0, 929 | "license": { 930 | "key": "apache-2.0", 931 | "name": "Apache License 2.0", 932 | "spdx_id": "Apache-2.0", 933 | "url": "https://api.github.com/licenses/apache-2.0", 934 | "node_id": "MDc6TGljZW5zZTI=" 935 | }, 936 | "forks": 15, 937 | "open_issues": 0, 938 | "watchers": 15, 939 | "default_branch": "master", 940 | "permissions": { 941 | "admin": False, 942 | "push": False, 943 | "pull": True 944 | } 945 | } 946 | ], 947 | [ 948 | 'episodes.dart', 949 | 'cpp-netlib', 950 | 'dagger', 951 | 'ios-webkit-debug-proxy', 952 | 'google.github.io', 953 | 'kratu', 954 | 'build-debian-cloud', 955 | 'traceur-compiler', 956 | 'firmata.py' 957 | ], 958 | ['dagger', 'kratu', 'traceur-compiler', 'firmata.py'], 959 | ) 960 | ] 961 | -------------------------------------------------------------------------------- /0x03-Unittests_and_integration_tests/test_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """A module for testing the client module. 3 | """ 4 | import unittest 5 | from typing import Dict 6 | from unittest.mock import ( 7 | MagicMock, 8 | Mock, 9 | PropertyMock, 10 | patch, 11 | ) 12 | from parameterized import parameterized, parameterized_class 13 | from requests import HTTPError 14 | 15 | from client import ( 16 | GithubOrgClient 17 | ) 18 | from fixtures import TEST_PAYLOAD 19 | 20 | 21 | class TestGithubOrgClient(unittest.TestCase): 22 | """Tests the `GithubOrgClient` class.""" 23 | @parameterized.expand([ 24 | ("google", {'login': "google"}), 25 | ("abc", {'login': "abc"}), 26 | ]) 27 | @patch( 28 | "client.get_json", 29 | ) 30 | def test_org(self, org: str, resp: Dict, mocked_fxn: MagicMock) -> None: 31 | """Tests the `org` method.""" 32 | mocked_fxn.return_value = MagicMock(return_value=resp) 33 | gh_org_client = GithubOrgClient(org) 34 | self.assertEqual(gh_org_client.org(), resp) 35 | mocked_fxn.assert_called_once_with( 36 | "https://api.github.com/orgs/{}".format(org) 37 | ) 38 | 39 | def test_public_repos_url(self) -> None: 40 | """Tests the `_public_repos_url` property.""" 41 | with patch( 42 | "client.GithubOrgClient.org", 43 | new_callable=PropertyMock, 44 | ) as mock_org: 45 | mock_org.return_value = { 46 | 'repos_url': "https://api.github.com/users/google/repos", 47 | } 48 | self.assertEqual( 49 | GithubOrgClient("google")._public_repos_url, 50 | "https://api.github.com/users/google/repos", 51 | ) 52 | 53 | @patch("client.get_json") 54 | def test_public_repos(self, mock_get_json: MagicMock) -> None: 55 | """Tests the `public_repos` method.""" 56 | test_payload = { 57 | 'repos_url': "https://api.github.com/users/google/repos", 58 | 'repos': [ 59 | { 60 | "id": 7697149, 61 | "name": "episodes.dart", 62 | "private": False, 63 | "owner": { 64 | "login": "google", 65 | "id": 1342004, 66 | }, 67 | "fork": False, 68 | "url": "https://api.github.com/repos/google/episodes.dart", 69 | "created_at": "2013-01-19T00:31:37Z", 70 | "updated_at": "2019-09-23T11:53:58Z", 71 | "has_issues": True, 72 | "forks": 22, 73 | "default_branch": "master", 74 | }, 75 | { 76 | "id": 8566972, 77 | "name": "kratu", 78 | "private": False, 79 | "owner": { 80 | "login": "google", 81 | "id": 1342004, 82 | }, 83 | "fork": False, 84 | "url": "https://api.github.com/repos/google/kratu", 85 | "created_at": "2013-03-04T22:52:33Z", 86 | "updated_at": "2019-11-15T22:22:16Z", 87 | "has_issues": True, 88 | "forks": 32, 89 | "default_branch": "master", 90 | }, 91 | ] 92 | } 93 | mock_get_json.return_value = test_payload["repos"] 94 | with patch( 95 | "client.GithubOrgClient._public_repos_url", 96 | new_callable=PropertyMock, 97 | ) as mock_public_repos_url: 98 | mock_public_repos_url.return_value = test_payload["repos_url"] 99 | self.assertEqual( 100 | GithubOrgClient("google").public_repos(), 101 | [ 102 | "episodes.dart", 103 | "kratu", 104 | ], 105 | ) 106 | mock_public_repos_url.assert_called_once() 107 | mock_get_json.assert_called_once() 108 | 109 | @parameterized.expand([ 110 | ({'license': {'key': "bsd-3-clause"}}, "bsd-3-clause", True), 111 | ({'license': {'key': "bsl-1.0"}}, "bsd-3-clause", False), 112 | ]) 113 | def test_has_license(self, repo: Dict, key: str, expected: bool) -> None: 114 | """Tests the `has_license` method.""" 115 | gh_org_client = GithubOrgClient("google") 116 | client_has_licence = gh_org_client.has_license(repo, key) 117 | self.assertEqual(client_has_licence, expected) 118 | 119 | 120 | @parameterized_class([ 121 | { 122 | 'org_payload': TEST_PAYLOAD[0][0], 123 | 'repos_payload': TEST_PAYLOAD[0][1], 124 | 'expected_repos': TEST_PAYLOAD[0][2], 125 | 'apache2_repos': TEST_PAYLOAD[0][3], 126 | }, 127 | ]) 128 | class TestIntegrationGithubOrgClient(unittest.TestCase): 129 | """Performs integration tests for the `GithubOrgClient` class.""" 130 | @classmethod 131 | def setUpClass(cls) -> None: 132 | """Sets up class fixtures before running tests.""" 133 | route_payload = { 134 | 'https://api.github.com/orgs/google': cls.org_payload, 135 | 'https://api.github.com/orgs/google/repos': cls.repos_payload, 136 | } 137 | 138 | def get_payload(url): 139 | if url in route_payload: 140 | return Mock(**{'json.return_value': route_payload[url]}) 141 | return HTTPError 142 | 143 | cls.get_patcher = patch("requests.get", side_effect=get_payload) 144 | cls.get_patcher.start() 145 | 146 | def test_public_repos(self) -> None: 147 | """Tests the `public_repos` method.""" 148 | self.assertEqual( 149 | GithubOrgClient("google").public_repos(), 150 | self.expected_repos, 151 | ) 152 | 153 | def test_public_repos_with_license(self) -> None: 154 | """Tests the `public_repos` method with a license.""" 155 | self.assertEqual( 156 | GithubOrgClient("google").public_repos(license="apache-2.0"), 157 | self.apache2_repos, 158 | ) 159 | 160 | @classmethod 161 | def tearDownClass(cls) -> None: 162 | """Removes the class fixtures after running all tests.""" 163 | cls.get_patcher.stop() 164 | -------------------------------------------------------------------------------- /0x03-Unittests_and_integration_tests/test_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """A module for testing the utils module. 3 | """ 4 | import unittest 5 | from typing import Dict, Tuple, Union 6 | from unittest.mock import patch, Mock 7 | from parameterized import parameterized 8 | 9 | from utils import ( 10 | access_nested_map, 11 | get_json, 12 | memoize, 13 | ) 14 | 15 | 16 | class TestAccessNestedMap(unittest.TestCase): 17 | """Tests the `access_nested_map` function.""" 18 | @parameterized.expand([ 19 | ({"a": 1}, ("a",), 1), 20 | ({"a": {"b": 2}}, ("a",), {"b": 2}), 21 | ({"a": {"b": 2}}, ("a", "b"), 2), 22 | ]) 23 | def test_access_nested_map( 24 | self, 25 | nested_map: Dict, 26 | path: Tuple[str], 27 | expected: Union[Dict, int], 28 | ) -> None: 29 | """Tests `access_nested_map`'s output.""" 30 | self.assertEqual(access_nested_map(nested_map, path), expected) 31 | 32 | @parameterized.expand([ 33 | ({}, ("a",), KeyError), 34 | ({"a": 1}, ("a", "b"), KeyError), 35 | ]) 36 | def test_access_nested_map_exception( 37 | self, 38 | nested_map: Dict, 39 | path: Tuple[str], 40 | exception: Exception, 41 | ) -> None: 42 | """Tests `access_nested_map`'s exception raising.""" 43 | with self.assertRaises(exception): 44 | access_nested_map(nested_map, path) 45 | 46 | 47 | class TestGetJson(unittest.TestCase): 48 | """Tests the `get_json` function.""" 49 | @parameterized.expand([ 50 | ("http://example.com", {"payload": True}), 51 | ("http://holberton.io", {"payload": False}), 52 | ]) 53 | def test_get_json( 54 | self, 55 | test_url: str, 56 | test_payload: Dict, 57 | ) -> None: 58 | """Tests `get_json`'s output.""" 59 | attrs = {'json.return_value': test_payload} 60 | with patch("requests.get", return_value=Mock(**attrs)) as req_get: 61 | self.assertEqual(get_json(test_url), test_payload) 62 | req_get.assert_called_once_with(test_url) 63 | 64 | 65 | class TestMemoize(unittest.TestCase): 66 | """Tests the `memoize` function.""" 67 | def test_memoize(self) -> None: 68 | """Tests `memoize`'s output.""" 69 | class TestClass: 70 | def a_method(self): 71 | return 42 72 | 73 | @memoize 74 | def a_property(self): 75 | return self.a_method() 76 | with patch.object( 77 | TestClass, 78 | "a_method", 79 | return_value=lambda: 42, 80 | ) as memo_fxn: 81 | test_class = TestClass() 82 | self.assertEqual(test_class.a_property(), 42) 83 | self.assertEqual(test_class.a_property(), 42) 84 | memo_fxn.assert_called_once() 85 | -------------------------------------------------------------------------------- /0x03-Unittests_and_integration_tests/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Generic utilities for github org client. 3 | """ 4 | import requests 5 | from functools import wraps 6 | from typing import ( 7 | Mapping, 8 | Sequence, 9 | Any, 10 | Dict, 11 | Callable, 12 | ) 13 | 14 | 15 | __all__ = [ 16 | "access_nested_map", 17 | "get_json", 18 | "memoize", 19 | ] 20 | 21 | 22 | def access_nested_map(nested_map: Mapping, path: Sequence) -> Any: 23 | """Access nested map with key path. 24 | Parameters 25 | ---------- 26 | nested_map: Mapping 27 | A nested map 28 | path: Sequence 29 | a sequence of key representing a path to the value 30 | Example 31 | ------- 32 | >>> nested_map = {"a": {"b": {"c": 1}}} 33 | >>> access_nested_map(nested_map, ["a", "b", "c"]) 34 | 1 35 | """ 36 | for key in path: 37 | if not isinstance(nested_map, Mapping): 38 | raise KeyError(key) 39 | nested_map = nested_map[key] 40 | 41 | return nested_map 42 | 43 | 44 | def get_json(url: str) -> Dict: 45 | """Get JSON from remote URL. 46 | """ 47 | response = requests.get(url) 48 | return response.json() 49 | 50 | 51 | def memoize(fn: Callable) -> Callable: 52 | """Decorator to memoize a method. 53 | Example 54 | ------- 55 | class MyClass: 56 | @memoize 57 | def a_method(self): 58 | print("a_method called") 59 | return 42 60 | >>> my_object = MyClass() 61 | >>> my_object.a_method 62 | a_method called 63 | 42 64 | >>> my_object.a_method 65 | 42 66 | """ 67 | attr_name = "_{}".format(fn.__name__) 68 | 69 | @wraps(fn) 70 | def memoized(self): 71 | """"memoized wraps""" 72 | if not hasattr(self, attr_name): 73 | setattr(self, attr_name, fn(self)) 74 | return getattr(self, attr_name) 75 | 76 | return property(memoized) 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Bezaleel Olakunori 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ALX Backend Python 2 | 3 | ![Repo size](https://img.shields.io/github/repo-size/B3zaleel/alx-backend-python) 4 | ![Pep8 style](https://img.shields.io/badge/PEP8-style%20guide-purple?style=round-square) 5 | ![Latest commit](https://img.shields.io/github/last-commit/B3zaleel/alx-backend-python/main?style=round-square) 6 | 7 | This repo contains projects for learning backend development concepts with __Python__. 8 | --------------------------------------------------------------------------------