├── .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 | 
4 | 
5 | 
6 |
7 | This repo contains projects for learning backend development concepts with __Python__.
8 |
--------------------------------------------------------------------------------