├── 0x00-python_variable_annotations ├── .gitignore ├── 0-add.py ├── 0-main.py ├── 1-concat.py ├── 1-main.py ├── 100-main.py ├── 100-safe_first_element.py ├── 101-main.py ├── 101-safely_get_value.py ├── 102-main.py ├── 102-type_checking.py ├── 2-floor.py ├── 2-main.py ├── 3-main.py ├── 3-to_str.py ├── 4-define_variables.py ├── 4-main.py ├── 5-main.py ├── 5-sum_list.py ├── 6-main.py ├── 6-sum_mixed_list.py ├── 7-main.py ├── 7-to_kv.py ├── 8-main.py ├── 8-make_multiplier.py ├── 9-element_length.py ├── 9-main.py └── README.md ├── 0x01-python_async_function ├── 0-basic_async_syntax.py ├── 0-main.py ├── 1-concurrent_coroutines.py ├── 1-main.py ├── 2-main.py ├── 2-measure_runtime.py ├── 3-main.py ├── 3-tasks.py ├── 4-main.py ├── 4-tasks.py └── README.md ├── 0x02-python_async_comprehension ├── 0-async_generator.py ├── 0-main.py ├── 1-async_comprehension.py ├── 1-main.py ├── 2-main.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 └── README.md /0x00-python_variable_annotations/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /0x00-python_variable_annotations/0-add.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for adding two numbers. 4 | """ 5 | 6 | 7 | def add(a: float, b: float) -> float: 8 | """ 9 | This function adds two numbers and returns the result. 10 | 11 | Parameters: 12 | a (float): The first number. 13 | b (float): The second number. 14 | 15 | Returns: 16 | float: The sum of a and b. 17 | """ 18 | return a + b 19 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/0-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | add = __import__('0-add').add 3 | 4 | print(add(1.11, 2.22) == 1.11 + 2.22) 5 | print(add.__annotations__) 6 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/1-concat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for concatenating two strings. 4 | """ 5 | 6 | 7 | def concat(str1: str, str2: str) -> str: 8 | """ 9 | This function concatenates two strings and returns the result. 10 | 11 | Parameters: 12 | str1 (str): The first string. 13 | str2 (str): The second string. 14 | 15 | Returns: 16 | str: The concatenation of str1 and str2. 17 | """ 18 | return str1 + str2 19 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/1-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | concat = __import__('1-concat').concat 3 | 4 | str1 = "egg" 5 | str2 = "shell" 6 | 7 | print(concat(str1, str2) == "{}{}".format(str1, str2)) 8 | print(concat.__annotations__) 9 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/100-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | safe_first_element = __import__('100-safe_first_element').safe_first_element 4 | 5 | print(safe_first_element.__annotations__) 6 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/100-safe_first_element.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for returning the first element 4 | of a list, or None if the list is empty. 5 | """ 6 | from typing import Sequence, Union, Any 7 | 8 | 9 | def safe_first_element(lst: Sequence[Any]) -> Union[Any, None]: 10 | """ 11 | This function returns the first element of a sequence, or None if the 12 | sequence is empty. 13 | 14 | Parameters: 15 | lst (Sequence): The sequence to process. 16 | 17 | Returns: 18 | Union[object, None]: The first element of the sequence, or None if the 19 | sequence is empty. 20 | """ 21 | if lst: 22 | return lst[0] 23 | else: 24 | return None 25 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/101-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | safely_get_value = __import__('101-safely_get_value').safely_get_value 4 | annotations = safely_get_value.__annotations__ 5 | 6 | print("Here's what the mappings should look like") 7 | for k, v in annotations.items(): 8 | print( ("{}: {}".format(k, v))) 9 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/101-safely_get_value.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for safely getting a value from 4 | a mapping. 5 | """ 6 | from typing import Any, Mapping, Union, TypeVar 7 | 8 | T = TypeVar('T') 9 | Res = Union[Any, T] 10 | Def = Union[T, None] 11 | 12 | 13 | def safely_get_value(dct: Mapping, key: Any, default: Def = None) -> Res: 14 | """ 15 | This function gets the value for a given key in a mapping, or returns 16 | a default value if the key is not in the mapping. 17 | 18 | Parameters: 19 | dct (Mapping): The mapping to get the value from. 20 | key (Any): The key to look up in the mapping. 21 | default (Union[T, None]): The default value to return if the key is not 22 | in the mapping. 23 | 24 | Returns: 25 | Union[Any, T]: The value for the key in the mapping, or the default 26 | value if the key is not in the mapping. 27 | """ 28 | if key in dct: 29 | return dct[key] 30 | else: 31 | return default 32 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/102-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | zoom_array = __import__('102-type_checking').zoom_array 4 | 5 | print(zoom_array.__annotations__) 6 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/102-type_checking.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for zooming in on a tuple. 4 | """ 5 | from typing import List, Tuple 6 | 7 | 8 | def zoom_array(lst: Tuple, factor: int = 2) -> List: 9 | """ 10 | This function takes a tuple and a zoom factor, and returns a new list 11 | with the elements of the tuple repeated according to the zoom factor. 12 | 13 | Parameters: 14 | lst (Tuple): The tuple to zoom in on. 15 | factor (int): The zoom factor, which determines how many times each 16 | element of the tuple should be repeated in the output list. 17 | 18 | Returns: 19 | List: A new list with the elements of the tuple repeated according to 20 | the zoom factor. 21 | """ 22 | zoomed_in: List = [ 23 | item for item in lst 24 | for i in range(int(factor)) 25 | ] 26 | return zoomed_in 27 | 28 | 29 | array = (12, 72, 91) 30 | 31 | zoom_2x = zoom_array(array) 32 | 33 | zoom_3x = zoom_array(array, 3) 34 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/2-floor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for finding the floor of a float. 4 | """ 5 | import math 6 | 7 | 8 | def floor(n: float) -> int: 9 | """ 10 | This function finds the floor of a float and returns the result as an 11 | integer. 12 | 13 | Parameters: 14 | n (float): The float to find the floor of. 15 | 16 | Returns: 17 | int: The floor of n. 18 | """ 19 | return math.floor(n) 20 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/2-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import math 4 | 5 | floor = __import__('2-floor').floor 6 | 7 | ans = floor(3.14) 8 | 9 | print(ans == math.floor(3.14)) 10 | print(floor.__annotations__) 11 | print("floor(3.14) returns {}, which is a {}".format(ans, type(ans))) 12 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/3-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | to_str = __import__('3-to_str').to_str 3 | 4 | pi_str = to_str(3.14) 5 | print(pi_str == str(3.14)) 6 | print(to_str.__annotations__) 7 | print("to_str(3.14) returns {} which is a {}".format(pi_str, type(pi_str))) 8 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/3-to_str.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for converting a float to a string. 4 | """ 5 | 6 | 7 | def to_str(n: float) -> str: 8 | """ 9 | This function converts a float to a string and returns the result. 10 | 11 | Parameters: 12 | n (float): The float to convert. 13 | 14 | Returns: 15 | str: The string representation of n. 16 | """ 17 | return str(n) 18 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/4-define_variables.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This module provides variables with type annotations. 4 | 5 | The variables are: 6 | - a: an integer with the value 1 7 | - pi: a float with the value 3.14 8 | - i_understand_annotations: a boolean with the value True 9 | - school: a string with the value 'Holberton' 10 | """ 11 | 12 | a: int = 1 13 | pi: float = 3.14 14 | i_understand_annotations: bool = True 15 | school: str = 'Holberton' 16 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/4-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | a = __import__('4-define_variables').a 4 | pi = __import__('4-define_variables').pi 5 | i_understand_annotations = __import__('4-define_variables').i_understand_annotations 6 | school = __import__('4-define_variables').school 7 | 8 | print("a is a {} with a value of {}".format(type(a), a)) 9 | print("pi is a {} with a value of {}".format(type(pi), pi)) 10 | print("i_understand_annotations is a {} with a value of {}".format(type(i_understand_annotations), i_understand_annotations)) 11 | print("school is a {} with a value of {}".format(type(school), school)) 12 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/5-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | sum_list = __import__('5-sum_list').sum_list 4 | 5 | floats = [3.14, 1.11, 2.22] 6 | floats_sum = sum_list(floats) 7 | print(floats_sum == sum(floats)) 8 | print(sum_list.__annotations__) 9 | print("sum_list(floats) returns {} which is a {}".format(floats_sum, type(floats_sum))) 10 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/5-sum_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for summing list values. 4 | """ 5 | from typing import List 6 | 7 | 8 | def sum_list(input_list: List[float]) -> float: 9 | """ 10 | This function adds two numbers and returns the result. 11 | 12 | Parameters: 13 | a (float): The first number. 14 | b (float): The second number. 15 | 16 | Returns: 17 | float: The sum of a and b. 18 | """ 19 | return sum(input_list) 20 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/6-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | sum_mixed_list = __import__('6-sum_mixed_list').sum_mixed_list 4 | 5 | print(sum_mixed_list.__annotations__) 6 | mixed = [5, 4, 3.14, 666, 0.99] 7 | ans = sum_mixed_list(mixed) 8 | print(ans == sum(mixed)) 9 | print("sum_mixed_list(mixed) returns {} which is a {}".format(ans, type(ans))) 10 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/6-sum_mixed_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for summing list values. 4 | """ 5 | from typing import Union, List 6 | 7 | 8 | def sum_mixed_list(mxd_lst: List[Union[int, float]]) -> float: 9 | """ 10 | This function computes the sum of a list of integers and floats, and 11 | returns the result as a float. 12 | 13 | Parameters: 14 | mxd_lst (List[Union[int, float]]): The list of integers and floats to sum. 15 | 16 | Returns: 17 | float: The sum of the elements in mxd_lst. 18 | """ 19 | return sum(mxd_lst) 20 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/7-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | to_kv = __import__('7-to_kv').to_kv 4 | 5 | print(to_kv.__annotations__) 6 | print(to_kv("eggs", 3)) 7 | print(to_kv("school", 0.02)) 8 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/7-to_kv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for creating a tuple with a string 4 | and the square of an int or float as a float. 5 | """ 6 | from typing import Union, Tuple 7 | 8 | 9 | def to_kv(k: str, v: Union[int, float]) -> Tuple[str, float]: 10 | """ 11 | This function takes a string and an int or float, and returns a tuple with 12 | the string and the square of the int or float as a float. 13 | 14 | Parameters: 15 | k (str): The string to include in the tuple. 16 | v (Union[int, float]): The int or float to square. 17 | 18 | Returns: 19 | Tuple[str, float]: A tuple with the string k and the square of v as a 20 | float. 21 | """ 22 | return (k, float(v ** 2)) 23 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/8-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | make_multiplier = __import__('8-make_multiplier').make_multiplier 4 | print(make_multiplier.__annotations__) 5 | fun = make_multiplier(2.22) 6 | print("{}".format(fun(2.22))) 7 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/8-make_multiplier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for creating a function that 4 | multiplies a float by a multiplier. 5 | """ 6 | from typing import Callable 7 | 8 | 9 | def make_multiplier(multiplier: float) -> Callable[[float], float]: 10 | """ 11 | This function takes a float as an argument and returns a function that 12 | multiplies a float by the multiplier. 13 | 14 | Parameters: 15 | multiplier (float): The multiplier to use in the returned function. 16 | 17 | Returns: 18 | Callable[[float], float]: A function that multiplies a float by the 19 | multiplier. 20 | """ 21 | def multiply(n: float) -> float: 22 | """ 23 | This function multiplies a float by the multiplier. 24 | 25 | Parameters: 26 | n (float): The float to multiply by the multiplier. 27 | 28 | Returns: 29 | float: The result of multiplying n by the multiplier. 30 | """ 31 | return n * multiplier 32 | return multiply 33 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/9-element_length.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a module that provides a function for finding the length of each 4 | element in a list of strings. 5 | """ 6 | from typing import Iterable, List, Sequence, Tuple 7 | 8 | 9 | def element_length(lst: Iterable[Sequence]) -> List[Tuple[Sequence, int]]: 10 | """ 11 | This function takes a list of strings and returns a list of tuples, 12 | where each tuple contains a string and the length of that string. 13 | 14 | Parameters: 15 | lst (List[str]): The list of strings to process. 16 | 17 | Returns: 18 | List[Tuple[str, int]]: A list of tuples, where each tuple contains 19 | a string and the length of that string. 20 | """ 21 | return [(i, len(i)) for i in lst] 22 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/9-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | element_length = __import__('9-element_length').element_length 4 | 5 | print(element_length.__annotations__) 6 | -------------------------------------------------------------------------------- /0x00-python_variable_annotations/README.md: -------------------------------------------------------------------------------- 1 | # :book: 0x00. Python - Variable Annotations 2 | ## :page_with_curl: Topics Covered 3 | 1. Python Type Annotation. 4 | 5 | # :computer: Tasks. 6 | ## [0. Basic annotations - add](0-main.py) 7 | ### :page_with_curl: Task requirements. 8 | Write a type-annotated function add that takes a float a and a float b as arguments and returns their sum as a float. 9 | ``` 10 | bob@dylan:~$ cat 0-main.py 11 | #!/usr/bin/env python3 12 | add = __import__('0-add').add 13 | 14 | print(add(1.11, 2.22) == 1.11 + 2.22) 15 | print(add.__annotations__) 16 | 17 | bob@dylan:~$ ./0-main.py 18 | True 19 | {'a': , 'b': , 'return': } 20 | ``` 21 | 22 | ### :wrench: Task setup. 23 | ```bash 24 | # Create task files and set execute permission. 25 | touch 0-add.py 26 | chmod +x 0-add.py 27 | ./0-add.py 28 | 29 | # Lint checks 30 | pycodestyle 0-add.py 31 | mypy 0-add.py 32 | 33 | # Tests 34 | touch 0-main.py 35 | chmod +x 0-main.py 36 | ./0-main.py 37 | ``` 38 | 39 | ### :heavy_check_mark: Solution 40 | > [:point_right: 0-main.py](0-main.py) 41 | 42 | 43 | ## [1. Basic annotations - concat](1-concat.py) 44 | ### :page_with_curl: Task requirements. 45 | Write a type-annotated function add that takes a float a and a float b as arguments and returns their sum as a float. 46 | ``` 47 | bob@dylan:~$ cat 1-concat.py 48 | #!/usr/bin/env python3 49 | add = __import__('0-add').add 50 | 51 | print(add(1.11, 2.22) == 1.11 + 2.22) 52 | print(add.__annotations__) 53 | 54 | bob@dylan:~$ ./1-concat.py 55 | True 56 | {'a': , 'b': , 'return': } 57 | ``` 58 | 59 | ### :wrench: Task setup. 60 | ```bash 61 | # Create task files and set execute permission. 62 | touch 1-concat.py 63 | chmod +x 1-concat.py 64 | ./1-concat.py 65 | 66 | pycodestyle 1-concat.py 67 | mypy 1-concat.py 68 | 69 | # Tests 70 | touch 1-main.py 71 | chmod +x 1-main.py 72 | ./1-main.py 73 | ``` 74 | 75 | ### :heavy_check_mark: Solution 76 | > [:point_right: 1-concat.py](1-concat.py) 77 | 78 | 79 | ## [2. Basic annotations - floor](2-main.py) 80 | ### :page_with_curl: Task requirements. 81 | Write a type-annotated function floor which takes a float n as argument and returns the floor of the float. 82 | ``` 83 | bob@dylan:~$ cat 2-main.py 84 | #!/usr/bin/env python3 85 | 86 | import math 87 | 88 | floor = __import__('2-floor').floor 89 | 90 | ans = floor(3.14) 91 | 92 | print(ans == math.floor(3.14)) 93 | print(floor.__annotations__) 94 | print("floor(3.14) returns {}, which is a {}".format(ans, type(ans))) 95 | 96 | bob@dylan:~$ ./2-main.py 97 | True 98 | {'n': , 'return': } 99 | floor(3.14) returns 3, which is a 100 | ``` 101 | 102 | ### :wrench: Task setup. 103 | ```bash 104 | # Create task files and set execute permission. 105 | touch 2-floor.py 106 | chmod +x 2-floor.py 107 | ./2-floor.py 108 | 109 | pycodestyle 2-floor.py 110 | mypy 2-floor.py 111 | 112 | # Tests 113 | touch 2-main.py 114 | chmod +x 2-main.py 115 | ./2-main.py 116 | ``` 117 | 118 | ### :heavy_check_mark: Solution 119 | > [:point_right: 2-main.py](2-main.py) 120 | 121 | 122 | ## [3. Basic annotations - to string](3-main.py) 123 | ### :page_with_curl: Task requirements. 124 | Write a type-annotated function to_str that takes a float n as argument and returns the string representation of the float. 125 | ``` 126 | bob@dylan:~$ cat 3-main.py 127 | #!/usr/bin/env python3 128 | to_str = __import__('3-to_str').to_str 129 | 130 | pi_str = to_str(3.14) 131 | print(pi_str == str(3.14)) 132 | print(to_str.__annotations__) 133 | print("to_str(3.14) returns {} which is a {}".format(pi_str, type(pi_str))) 134 | 135 | bob@dylan:~$ ./3-main.py 136 | True 137 | {'n': , 'return': } 138 | to_str(3.14) returns 3.14, which is a 139 | ``` 140 | 141 | ### :wrench: Task setup. 142 | ```bash 143 | # Create task files and set execute permission. 144 | touch 3-to_str.py 145 | chmod +x 3-to_str.py 146 | ./3-to_str.py 147 | 148 | pycodestyle 3-to_str.py 149 | mypy 3-to_str.py 150 | 151 | # Tests 152 | touch 3-main.py 153 | chmod +x 3-main.py 154 | ./3-main.py 155 | ``` 156 | 157 | ### :heavy_check_mark: Solution 158 | > [:point_right: 3-main.py](3-main.py) 159 | 160 | 161 | ## [4. Define variables](4-main.py) 162 | ### :page_with_curl: Task requirements. 163 | Define and annotate the following variables with the specified values: 164 | 165 | * a, an integer with a value of 1 166 | * pi, a float with a value of 3.14 167 | * i_understand_annotations, a boolean with a value of True 168 | * school, a string with a value of “Holberton” 169 | ``` 170 | bob@dylan:~$ cat 4-main.py 171 | #!/usr/bin/env python3 172 | 173 | a = __import__('4-define_variables').a 174 | pi = __import__('4-define_variables').pi 175 | i_understand_annotations = __import__('4-define_variables').i_understand_annotations 176 | school = __import__('4-define_variables').school 177 | 178 | print("a is a {} with a value of {}".format(type(a), a)) 179 | print("pi is a {} with a value of {}".format(type(pi), pi)) 180 | print("i_understand_annotations is a {} with a value of {}".format(type(i_understand_annotations), i_understand_annotations)) 181 | print("school is a {} with a value of {}".format(type(school), school)) 182 | 183 | bob@dylan:~$ ./4-main.py 184 | a is a with a value of 1 185 | pi is a with a value of 3.14 186 | i_understand_annotations is a with a value of True 187 | school is a with a value of Holberton 188 | ``` 189 | 190 | ### :wrench: Task setup. 191 | ```bash 192 | # Create task files and set execute permission. 193 | touch 4-define_variables.py 194 | chmod +x 4-define_variables.py 195 | ./4-define_variables.py 196 | 197 | pycodestyle 4-define_variables.py 198 | mypy 4-define_variables.py 199 | 200 | # Tests 201 | touch 4-main.py 202 | chmod +x 4-main.py 203 | ./4-main.py 204 | ``` 205 | 206 | ### :heavy_check_mark: Solution 207 | > [:point_right: 4-main.py](4-main.py) 208 | 209 | 210 | ## [5. Complex types - list of floats](5-main.py) 211 | ### :page_with_curl: Task requirements. 212 | Write a type-annotated function sum_list which takes a list input_list of floats as argument and returns their sum as a float. 213 | ``` 214 | bob@dylan:~$ cat 5-main.py 215 | #!/usr/bin/env python3 216 | 217 | sum_list = __import__('5-sum_list').sum_list 218 | 219 | floats = [3.14, 1.11, 2.22] 220 | floats_sum = sum_list(floats) 221 | print(floats_sum == sum(floats)) 222 | print(sum_list.__annotations__) 223 | print("sum_list(floats) returns {} which is a {}".format(floats_sum, type(floats_sum))) 224 | 225 | bob@dylan:~$ ./5-main.py 226 | True 227 | {'input_list': typing.List[float], 'return': } 228 | sum_list(floats) returns 6.470000000000001 which is a 229 | ``` 230 | 231 | ### :wrench: Task setup. 232 | ```bash 233 | # Create task files and set execute permission. 234 | touch 5-sum_list.py 235 | chmod +x 5-sum_list.py 236 | ./5-sum_list.py 237 | 238 | # Tests 239 | touch 5-main.py 240 | chmod +x 5-main.py 241 | ./5-main.py 242 | 243 | # Lint 244 | pycodestyle 5-sum_list.py 245 | mypy 5-sum_list.py 246 | ``` 247 | 248 | ### :heavy_check_mark: Solution 249 | > [:point_right: 5-main.py](5-main.py) 250 | 251 | 252 | ## [0. Basic annotations - add](5-main.py) 253 | ### :page_with_curl: Task requirements. 254 | Write a type-annotated function add that takes a float a and a float b as arguments and returns their sum as a float. 255 | ``` 256 | bob@dylan:~$ cat 5-main.py 257 | #!/usr/bin/env python3 258 | add = __import__('0-add').add 259 | 260 | print(add(1.11, 2.22) == 1.11 + 2.22) 261 | print(add.__annotations__) 262 | 263 | bob@dylan:~$ ./5-main.py 264 | True 265 | {'a': , 'b': , 'return': } 266 | ``` 267 | 268 | ### :wrench: Task setup. 269 | ```bash 270 | # Create task files and set execute permission. 271 | touch 6-sum_mixed_list.py 272 | chmod +x 6-sum_mixed_list.py 273 | ./6-sum_mixed_list.py 274 | 275 | # Tests 276 | touch 6-main.py 277 | chmod +x 6-main.py 278 | ./6-main.py 279 | 280 | # Lint 281 | pycodestyle 6-sum_mixed_list.py 282 | mypy 6-sum_mixed_list.py 283 | ``` 284 | 285 | ### :heavy_check_mark: Solution 286 | > [:point_right: 5-main.py](5-main.py) 287 | 288 | 289 | ## [7. Complex types - string and int/float to tuple](7-main.py) 290 | ### :page_with_curl: Task requirements. 291 | Write 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. 292 | ``` 293 | bob@dylan:~$ cat 7-main.py 294 | #!/usr/bin/env python3 295 | 296 | to_kv = __import__('7-to_kv').to_kv 297 | 298 | print(to_kv.__annotations__) 299 | print(to_kv("eggs", 3)) 300 | print(to_kv("school", 0.02)) 301 | 302 | bob@dylan:~$ ./7-main.py 303 | {'k': , 'v': typing.Union[int, float], 'return': typing.Tuple[str, float]} 304 | ('eggs', 9) 305 | ('school', 0.0004) 306 | ``` 307 | 308 | ### :wrench: Task setup. 309 | ```bash 310 | # Create task files and set execute permission. 311 | touch 7-to_kv.py 312 | chmod +x 7-to_kv.py 313 | ./7-to_kv.py 314 | 315 | # Tests 316 | touch 7-main.py 317 | chmod +x 7-main.py 318 | ./7-main.py 319 | 320 | # Lint 321 | pycodestyle 7-to_kv.py 322 | mypy 7-to_kv.py 323 | ``` 324 | 325 | ### :heavy_check_mark: Solution 326 | > [:point_right: 7-main.py](7-main.py) 327 | 328 | 329 | ## [8. Complex types - functions](8-main.py) 330 | ### :page_with_curl: Task requirements. 331 | Write a type-annotated function make_multiplier that takes a float multiplier as argument and returns a function that multiplies a float by multiplier. 332 | 333 | ``` 334 | bob@dylan:~$ cat 8-main.py 335 | #!/usr/bin/env python3 336 | 337 | make_multiplier = __import__('8-make_multiplier').make_multiplier 338 | print(make_multiplier.__annotations__) 339 | fun = make_multiplier(2.22) 340 | print("{}".format(fun(2.22))) 341 | 342 | bob@dylan:~$ ./8-main.py 343 | {'multiplier': , 'return': typing.Callable[[float], float]} 344 | 4.928400000000001 345 | ``` 346 | 347 | ### :wrench: Task setup. 348 | ```bash 349 | # Create task files and set execute permission. 350 | touch 8-make_multiplier.py 351 | chmod +x 8-make_multiplier.py 352 | ./8-make_multiplier.py 353 | 354 | # Tests 355 | touch 8-main.py 356 | chmod +x 8-main.py 357 | ./8-main.py 358 | 359 | # Lint 360 | pycodestyle 8-make_multiplier.py 361 | mypy 8-make_multiplier.py 362 | ``` 363 | 364 | ### :heavy_check_mark: Solution 365 | > [:point_right: 8-main.py](8-main.py) 366 | 367 | 368 | ## [9. Let's duck type an iterable object](9-main.py) 369 | ### :page_with_curl: Task requirements. 370 | Annotate the below function’s parameters and return values with the appropriate types 371 | ``` 372 | def element_length(lst): 373 | return [(i, len(i)) for i in lst] 374 | ``` 375 | 376 | ``` 377 | bob@dylan:~$ cat 9-main.py 378 | #!/usr/bin/env python3 379 | 380 | element_length = __import__('9-element_length').element_length 381 | 382 | print(element_length.__annotations__) 383 | 384 | bob@dylan:~$ ./9-main.py 385 | {'lst': typing.Iterable[typing.Sequence], 'return': typing.List[typing.Tuple[typing.Sequence, int]]} 386 | ``` 387 | 388 | ### :wrench: Task setup. 389 | ```bash 390 | # Create task files and set execute permission. 391 | touch 9-element_length.py 392 | chmod +x 9-element_length.py 393 | ./9-element_length.py 394 | 395 | # Tests 396 | touch 9-main.py 397 | chmod +x 9-main.py 398 | ./9-main.py 399 | 400 | # Lint 401 | pycodestyle 9-element_length.py 402 | mypy 9-element_length.py 403 | ``` 404 | 405 | ### :heavy_check_mark: Solution 406 | > [:point_right: 9-main.py](9-main.py) 407 | 408 | 409 | ## [10. Duck typing - first element of a sequence](100-main.py) 410 | ### :page_with_curl: Task requirements. 411 | Augment the following code with the correct duck-typed annotations: 412 | ``` 413 | # The types of the elements of the input are not know 414 | def safe_first_element(lst): 415 | if lst: 416 | return lst[0] 417 | else: 418 | return None 419 | ``` 420 | 421 | ``` 422 | bob@dylan:~$ cat 100-main.py 423 | #!/usr/bin/env python3 424 | 425 | safe_first_element = __import__('100-safe_first_element').safe_first_element 426 | 427 | print(safe_first_element.__annotations__) 428 | 429 | bob@dylan:~$ ./100-main.py 430 | {'lst': typing.Sequence[typing.Any], 'return': typing.Union[typing.Any, NoneType]} 431 | ``` 432 | 433 | ### :wrench: Task setup. 434 | ```bash 435 | # Create task files and set execute permission. 436 | touch 100-safe_first_element.py 437 | chmod +x 100-safe_first_element.py 438 | ./100-safe_first_element.py 439 | 440 | # Tests 441 | touch 100-main.py 442 | chmod +x 100-main.py 443 | ./100-main.py 444 | 445 | # Lint 446 | pycodestyle 100-safe_first_element.py 447 | mypy 100-safe_first_element.py 448 | ``` 449 | 450 | ### :heavy_check_mark: Solution 451 | > [:point_right: 100-main.py](100-main.py) 452 | 453 | 454 | ## [11. More involved type annotations](101-main.py) 455 | ### :page_with_curl: Task requirements. 456 | Given the parameters and the return values, add type annotations to the function 457 | 458 | Hint: look into TypeVar 459 | ``` 460 | def safely_get_value(dct, key, default = None): 461 | if key in dct: 462 | return dct[key] 463 | else: 464 | return default 465 | ``` 466 | 467 | ``` 468 | bob@dylan:~$ cat 101-main.py 469 | #!/usr/bin/env python3 470 | 471 | safely_get_value = __import__('101-safely_get_value').safely_get_value 472 | annotations = safely_get_value.__annotations__ 473 | 474 | print("Here's what the mappings should look like") 475 | for k, v in annotations.items(): 476 | print( ("{}: {}".format(k, v))) 477 | 478 | bob@dylan:~$ ./101-main.py 479 | Here's what the mappings should look like 480 | dct: typing.Mapping 481 | key: typing.Any 482 | default: typing.Union[~T, NoneType] 483 | return: typing.Union[typing.Any, ~T] 484 | ``` 485 | 486 | ### :wrench: Task setup. 487 | ```bash 488 | # Create task files and set execute permission. 489 | touch 101-safely_get_value.py 490 | chmod +x 101-safely_get_value.py 491 | ./101-safely_get_value.py 492 | 493 | # Tests 494 | touch 101-main.py 495 | chmod +x 101-main.py 496 | ./101-main.py 497 | 498 | # Lint 499 | pycodestyle 101-safely_get_value.py 500 | mypy 101-safely_get_value.py 501 | ``` 502 | 503 | ### :heavy_check_mark: Solution 504 | > [:point_right: 101-main.py](101-main.py) 505 | 506 | 507 | ## [0. Basic annotations - add](102-main.py) 508 | ### :page_with_curl: Task requirements. 509 | Write a type-annotated function add that takes a float a and a float b as arguments and returns their sum as a float. 510 | ``` 511 | bob@dylan:~$ cat 102-main.py 512 | #!/usr/bin/env python3 513 | add = __import__('0-add').add 514 | 515 | print(add(1.11, 2.22) == 1.11 + 2.22) 516 | print(add.__annotations__) 517 | 518 | bob@dylan:~$ ./102-main.py 519 | True 520 | {'a': , 'b': , 'return': } 521 | ``` 522 | 523 | ### :wrench: Task setup. 524 | ```bash 525 | # Create task files and set execute permission. 526 | touch 102-type_checking.py 527 | chmod +x 102-type_checking.py 528 | ./102-type_checking.py 529 | 530 | # Tests 531 | touch 102-main.py 532 | chmod +x 102-main.py 533 | ./102-main.py 534 | 535 | # Lint 536 | pycodestyle 102-type_checking.py 537 | mypy 102-type_checking.py 538 | ``` 539 | 540 | ### :heavy_check_mark: Solution 541 | > [:point_right: 102-main.py](102-main.py) -------------------------------------------------------------------------------- /0x01-python_async_function/0-basic_async_syntax.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | A module for asynchronous tasks using asyncio. 4 | """ 5 | 6 | import asyncio 7 | import random 8 | 9 | 10 | async def wait_random(max_delay: int = 10) -> float: 11 | """ 12 | Wait for a random amount of time up to `max_delay` seconds. 13 | Returns the amount of time waited. 14 | """ 15 | wait_time = random.random() * max_delay 16 | await asyncio.sleep(wait_time) 17 | return wait_time 18 | -------------------------------------------------------------------------------- /0x01-python_async_function/0-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | 5 | wait_random = __import__('0-basic_async_syntax').wait_random 6 | 7 | print(asyncio.run(wait_random())) 8 | print(asyncio.run(wait_random(5))) 9 | print(asyncio.run(wait_random(15))) 10 | -------------------------------------------------------------------------------- /0x01-python_async_function/1-concurrent_coroutines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | A module for asynchronous tasks using asyncio. 4 | """ 5 | import asyncio 6 | from typing import List 7 | wait_random = __import__('0-basic_async_syntax').wait_random 8 | 9 | 10 | async def wait_n(n: int, max_delay: int) -> List[float]: 11 | """ 12 | Wait for `n` random amounts of time up to `max_delay` seconds. 13 | Returns a list of the wait times, sorted in ascending order. 14 | """ 15 | wait_times = await asyncio.gather( 16 | *tuple(map(lambda _: wait_random(max_delay), range(n))) 17 | ) 18 | return sorted(wait_times) 19 | -------------------------------------------------------------------------------- /0x01-python_async_function/1-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Test file for printing the correct output of the wait_n coroutine 4 | ''' 5 | import asyncio 6 | 7 | wait_n = __import__('1-concurrent_coroutines').wait_n 8 | 9 | print(asyncio.run(wait_n(5, 5))) 10 | print(asyncio.run(wait_n(10, 7))) 11 | print(asyncio.run(wait_n(10, 0))) -------------------------------------------------------------------------------- /0x01-python_async_function/2-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | measure_time = __import__('2-measure_runtime').measure_time 4 | 5 | n = 5 6 | max_delay = 9 7 | 8 | print(measure_time(n, max_delay)) 9 | -------------------------------------------------------------------------------- /0x01-python_async_function/2-measure_runtime.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | A module for measuring the time it takes to run a given number of 4 | asynchronous tasks concurrently. 5 | """ 6 | import asyncio 7 | import time 8 | wait_n = __import__('1-concurrent_coroutines').wait_n 9 | 10 | 11 | def measure_time(n: int, max_delay: int) -> float: 12 | """ 13 | Measure the time it takes to run `wait_n` with `n` coroutines 14 | that wait for random amounts of time up to `max_delay` seconds. 15 | Returns the average time taken per coroutine. 16 | """ 17 | start_time = time.time() 18 | asyncio.run(wait_n(n, max_delay)) 19 | return (time.time() - start_time) / n 20 | -------------------------------------------------------------------------------- /0x01-python_async_function/3-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | 5 | task_wait_random = __import__('3-tasks').task_wait_random 6 | 7 | 8 | async def test(max_delay: int) -> float: 9 | task = task_wait_random(max_delay) 10 | await task 11 | print(task.__class__) 12 | 13 | asyncio.run(test(5)) 14 | -------------------------------------------------------------------------------- /0x01-python_async_function/3-tasks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | A module for creating asyncio Tasks that run asynchronous functions. 4 | """ 5 | import asyncio 6 | wait_random = __import__('0-basic_async_syntax').wait_random 7 | 8 | 9 | def task_wait_random(max_delay: int) -> asyncio.Task: 10 | """ 11 | Create an asyncio Task that waits for a random amount of time 12 | up to `max_delay` seconds when run. 13 | Returns the created Task object. 14 | """ 15 | return asyncio.create_task(wait_random(max_delay)) 16 | -------------------------------------------------------------------------------- /0x01-python_async_function/4-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | 5 | task_wait_n = __import__('4-tasks').task_wait_n 6 | 7 | n = 5 8 | max_delay = 6 9 | print(asyncio.run(task_wait_n(n, max_delay))) 10 | -------------------------------------------------------------------------------- /0x01-python_async_function/4-tasks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | A module for creating and running multiple asyncio Tasks concurrently. 4 | """ 5 | import asyncio 6 | from typing import List 7 | task_wait_random = __import__('3-tasks').task_wait_random 8 | 9 | 10 | async def task_wait_n(n: int, max_delay: int) -> List[float]: 11 | """ 12 | Create and run `n` asyncio Tasks that wait for random amounts of time 13 | up to `max_delay` seconds. Returns a list of the Task objects, sorted 14 | in ascending order by the time waited. 15 | """ 16 | wait_times = await asyncio.gather( 17 | *tuple(map(lambda _: task_wait_random(max_delay), range(n))) 18 | ) 19 | return sorted(wait_times) 20 | -------------------------------------------------------------------------------- /0x01-python_async_function/README.md: -------------------------------------------------------------------------------- 1 | # :book: 0x00. 0x01. Python - Async 2 | ## :page_with_curl: Topics Covered 3 | 1. Python Async. 4 | 5 | # :computer: Tasks. 6 | ## [0. The basics of async](0-basic_async_syntax.py) 7 | ### :page_with_curl: Task requirements. 8 | Write 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. 9 | 10 | Use the random module. 11 | ``` 12 | bob@dylan:~$ cat 0-main.py 13 | #!/usr/bin/env python3 14 | 15 | import asyncio 16 | 17 | wait_random = __import__('0-basic_async_syntax').wait_random 18 | 19 | print(asyncio.run(wait_random())) 20 | print(asyncio.run(wait_random(5))) 21 | print(asyncio.run(wait_random(15))) 22 | 23 | bob@dylan:~$ ./0-main.py 24 | 9.034261504534394 25 | 1.6216525464615306 26 | 10.634589756751769 27 | ``` 28 | 29 | ### :wrench: Task setup. 30 | ```bash 31 | # Create task files and set execute permission. 32 | touch 0-basic_async_syntax.py 33 | chmod +x 0-basic_async_syntax.py 34 | ./0-basic_async_syntax.py 35 | 36 | # Lint checks 37 | pycodestyle 0-basic_async_syntax.py 38 | mypy 0-basic_async_syntax.py 39 | 40 | # Tests 41 | touch 0-main.py 42 | chmod +x 0-main.py 43 | ./0-main.py 44 | ``` 45 | 46 | ### :heavy_check_mark: Solution 47 | > [:point_right: 0-basic_async_syntax.py](0-basic_async_syntax.py) 48 | 49 | 50 | ## [1. Let's execute multiple coroutines at the same time with async](1-concurrent_coroutines.py) 51 | ### :page_with_curl: Task requirements. 52 | 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. 53 | 54 | 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. 55 | ``` 56 | bob@dylan:~$ cat 1-main.py 57 | #!/usr/bin/env python3 58 | ''' 59 | Test file for printing the correct output of the wait_n coroutine 60 | ''' 61 | import asyncio 62 | 63 | wait_n = __import__('1-concurrent_coroutines').wait_n 64 | 65 | print(asyncio.run(wait_n(5, 5))) 66 | print(asyncio.run(wait_n(10, 7))) 67 | print(asyncio.run(wait_n(10, 0))) 68 | 69 | bob@dylan:~$ ./1-main.py 70 | [0.9693881173832269, 1.0264573845731002, 1.7992690129519855, 3.641373003434587, 4.500011569340617] 71 | [0.07256214141415429, 1.518551245602588, 3.355762808432721, 3.7032593997182923, 3.7796178143655546, 4.744537840582318, 5.50781365463315, 5.758942587637626, 6.109707751654879, 6.831351588271327] 72 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 73 | 74 | The output for your answers might look a little different and that’s okay. 75 | ``` 76 | 77 | ### :wrench: Task setup. 78 | ```bash 79 | # Create task files and set execute permission. 80 | touch 1-concurrent_coroutines.py 81 | chmod +x 1-concurrent_coroutines.py 82 | ./1-concurrent_coroutines.py 83 | 84 | pycodestyle 1-concurrent_coroutines.py 85 | mypy 1-concurrent_coroutines.py 86 | 87 | # Tests 88 | touch 1-main.py 89 | chmod +x 1-main.py 90 | ./1-main.py 91 | ``` 92 | 93 | ### :heavy_check_mark: Solution 94 | > [:point_right: 1-concurrent_coroutines.py](1-concurrent_coroutines.py) 95 | 96 | 97 | ## [2. Measure the runtime](2-measure_runtime.py) 98 | ### :page_with_curl: Task requirements. 99 | From the previous file, import wait_n into 2-measure_runtime.py. 100 | 101 | 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. 102 | 103 | Use the time module to measure an approximate elapsed time. 104 | ``` 105 | bob@dylan:~$ cat 2-main.py 106 | #!/usr/bin/env python3 107 | 108 | measure_time = __import__('2-measure_runtime').measure_time 109 | 110 | n = 5 111 | max_delay = 9 112 | 113 | print(measure_time(n, max_delay)) 114 | 115 | bob@dylan:~$ ./2-main.py 116 | 1.759705400466919 117 | ``` 118 | 119 | ### :wrench: Task setup. 120 | ```bash 121 | # Create task files and set execute permission. 122 | touch 2-measure_runtime.py 123 | chmod +x 2-measure_runtime.py 124 | ./2-measure_runtime.py 125 | 126 | pycodestyle 2-measure_runtime.py 127 | mypy 2-measure_runtime.py 128 | 129 | # Tests 130 | touch 2-main.py 131 | chmod +x 2-main.py 132 | ./2-main.py 133 | ``` 134 | 135 | ### :heavy_check_mark: Solution 136 | > [:point_right: 2-measure_runtime.py](2-measure_runtime.py) 137 | 138 | 139 | ## [3. Tasks](3-tasks.py) 140 | ### :page_with_curl: Task requirements. 141 | Import wait_random from 0-basic_async_syntax. 142 | 143 | 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. 144 | ``` 145 | bob@dylan:~$ cat 3-main.py 146 | #!/usr/bin/env python3 147 | 148 | import asyncio 149 | 150 | task_wait_random = __import__('3-tasks').task_wait_random 151 | 152 | 153 | async def test(max_delay: int) -> float: 154 | task = task_wait_random(max_delay) 155 | await task 156 | print(task.__class__) 157 | 158 | asyncio.run(test(5)) 159 | 160 | bob@dylan:~$ ./3-main.py 161 | 162 | ``` 163 | 164 | ### :wrench: Task setup. 165 | ```bash 166 | # Create task files and set execute permission. 167 | touch 3-tasks.py 168 | chmod +x 3-tasks.py 169 | ./3-tasks.py 170 | 171 | pycodestyle 3-tasks.py 172 | mypy 3-tasks.py 173 | 174 | # Tests 175 | touch 3-main.py 176 | chmod +x 3-main.py 177 | ./3-main.py 178 | ``` 179 | 180 | ### :heavy_check_mark: Solution 181 | > [:point_right: 3-main.py](3-tasks.py) 182 | 183 | 184 | ## [4. Tasks](4-main.py) 185 | ### :page_with_curl: Task requirements. 186 | 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. 187 | ``` 188 | bob@dylan:~$ cat 4-main.py 189 | #!/usr/bin/env python3 190 | 191 | import asyncio 192 | 193 | task_wait_n = __import__('4-tasks').task_wait_n 194 | 195 | n = 5 196 | max_delay = 6 197 | print(asyncio.run(task_wait_n(n, max_delay))) 198 | 199 | bob@dylan:~$ ./4-main.py 200 | [0.2261658205652346, 1.1942770588220557, 1.8410422186086628, 2.1457353803430523, 4.002505454641153] 201 | ``` 202 | 203 | ### :wrench: Task setup. 204 | ```bash 205 | # Create task files and set execute permission. 206 | touch 4-tasks.py 207 | chmod +x 4-tasks.py 208 | ./4-tasks.py 209 | 210 | pycodestyle 4-tasks.py 211 | mypy 4-tasks.py 212 | 213 | # Tests 214 | touch 4-main.py 215 | chmod +x 4-main.py 216 | ./4-main.py 217 | ``` 218 | 219 | ### :heavy_check_mark: Solution 220 | > [:point_right: 4-main.py](4-main.py) 221 | 222 | 223 | ## [5. Complex types - list of floats](5-main.py) 224 | ### :page_with_curl: Task requirements. 225 | Write a type-annotated function sum_list which takes a list input_list of floats as argument and returns their sum as a float. 226 | ``` 227 | bob@dylan:~$ cat 5-main.py 228 | #!/usr/bin/env python3 229 | 230 | sum_list = __import__('5-sum_list').sum_list 231 | 232 | floats = [3.14, 1.11, 2.22] 233 | floats_sum = sum_list(floats) 234 | print(floats_sum == sum(floats)) 235 | print(sum_list.__annotations__) 236 | print("sum_list(floats) returns {} which is a {}".format(floats_sum, type(floats_sum))) 237 | 238 | bob@dylan:~$ ./5-main.py 239 | True 240 | {'input_list': typing.List[float], 'return': } 241 | sum_list(floats) returns 6.470000000000001 which is a 242 | ``` 243 | 244 | ### :wrench: Task setup. 245 | ```bash 246 | # Create task files and set execute permission. 247 | touch 5-sum_list.py 248 | chmod +x 5-sum_list.py 249 | ./5-sum_list.py 250 | 251 | # Tests 252 | touch 5-main.py 253 | chmod +x 5-main.py 254 | ./5-main.py 255 | 256 | # Lint 257 | pycodestyle 5-sum_list.py 258 | mypy 5-sum_list.py 259 | ``` 260 | 261 | ### :heavy_check_mark: Solution 262 | > [:point_right: 5-main.py](5-main.py) 263 | 264 | 265 | ## [0. Basic annotations - add](5-main.py) 266 | ### :page_with_curl: Task requirements. 267 | Write a type-annotated function add that takes a float a and a float b as arguments and returns their sum as a float. 268 | ``` 269 | bob@dylan:~$ cat 5-main.py 270 | #!/usr/bin/env python3 271 | add = __import__('0-add').add 272 | 273 | print(add(1.11, 2.22) == 1.11 + 2.22) 274 | print(add.__annotations__) 275 | 276 | bob@dylan:~$ ./5-main.py 277 | True 278 | {'a': , 'b': , 'return': } 279 | ``` 280 | 281 | ### :wrench: Task setup. 282 | ```bash 283 | # Create task files and set execute permission. 284 | touch 6-sum_mixed_list.py 285 | chmod +x 6-sum_mixed_list.py 286 | ./6-sum_mixed_list.py 287 | 288 | # Tests 289 | touch 6-main.py 290 | chmod +x 6-main.py 291 | ./6-main.py 292 | 293 | # Lint 294 | pycodestyle 6-sum_mixed_list.py 295 | mypy 6-sum_mixed_list.py 296 | ``` 297 | 298 | ### :heavy_check_mark: Solution 299 | > [:point_right: 5-main.py](5-main.py) 300 | 301 | 302 | ## [7. Complex types - string and int/float to tuple](7-main.py) 303 | ### :page_with_curl: Task requirements. 304 | Write 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. 305 | ``` 306 | bob@dylan:~$ cat 7-main.py 307 | #!/usr/bin/env python3 308 | 309 | to_kv = __import__('7-to_kv').to_kv 310 | 311 | print(to_kv.__annotations__) 312 | print(to_kv("eggs", 3)) 313 | print(to_kv("school", 0.02)) 314 | 315 | bob@dylan:~$ ./7-main.py 316 | {'k': , 'v': typing.Union[int, float], 'return': typing.Tuple[str, float]} 317 | ('eggs', 9) 318 | ('school', 0.0004) 319 | ``` 320 | 321 | ### :wrench: Task setup. 322 | ```bash 323 | # Create task files and set execute permission. 324 | touch 7-to_kv.py 325 | chmod +x 7-to_kv.py 326 | ./7-to_kv.py 327 | 328 | # Tests 329 | touch 7-main.py 330 | chmod +x 7-main.py 331 | ./7-main.py 332 | 333 | # Lint 334 | pycodestyle 7-to_kv.py 335 | mypy 7-to_kv.py 336 | ``` 337 | 338 | ### :heavy_check_mark: Solution 339 | > [:point_right: 7-main.py](7-main.py) 340 | 341 | 342 | ## [8. Complex types - functions](8-main.py) 343 | ### :page_with_curl: Task requirements. 344 | Write a type-annotated function make_multiplier that takes a float multiplier as argument and returns a function that multiplies a float by multiplier. 345 | 346 | ``` 347 | bob@dylan:~$ cat 8-main.py 348 | #!/usr/bin/env python3 349 | 350 | make_multiplier = __import__('8-make_multiplier').make_multiplier 351 | print(make_multiplier.__annotations__) 352 | fun = make_multiplier(2.22) 353 | print("{}".format(fun(2.22))) 354 | 355 | bob@dylan:~$ ./8-main.py 356 | {'multiplier': , 'return': typing.Callable[[float], float]} 357 | 4.928400000000001 358 | ``` 359 | 360 | ### :wrench: Task setup. 361 | ```bash 362 | # Create task files and set execute permission. 363 | touch 8-make_multiplier.py 364 | chmod +x 8-make_multiplier.py 365 | ./8-make_multiplier.py 366 | 367 | # Tests 368 | touch 8-main.py 369 | chmod +x 8-main.py 370 | ./8-main.py 371 | 372 | # Lint 373 | pycodestyle 8-make_multiplier.py 374 | mypy 8-make_multiplier.py 375 | ``` 376 | 377 | ### :heavy_check_mark: Solution 378 | > [:point_right: 8-main.py](8-main.py) 379 | 380 | 381 | ## [9. Let's duck type an iterable object](9-main.py) 382 | ### :page_with_curl: Task requirements. 383 | Annotate the below function’s parameters and return values with the appropriate types 384 | ``` 385 | def element_length(lst): 386 | return [(i, len(i)) for i in lst] 387 | ``` 388 | 389 | ``` 390 | bob@dylan:~$ cat 9-main.py 391 | #!/usr/bin/env python3 392 | 393 | element_length = __import__('9-element_length').element_length 394 | 395 | print(element_length.__annotations__) 396 | 397 | bob@dylan:~$ ./9-main.py 398 | {'lst': typing.Iterable[typing.Sequence], 'return': typing.List[typing.Tuple[typing.Sequence, int]]} 399 | ``` 400 | 401 | ### :wrench: Task setup. 402 | ```bash 403 | # Create task files and set execute permission. 404 | touch 9-element_length.py 405 | chmod +x 9-element_length.py 406 | ./9-element_length.py 407 | 408 | # Tests 409 | touch 9-main.py 410 | chmod +x 9-main.py 411 | ./9-main.py 412 | 413 | # Lint 414 | pycodestyle 9-element_length.py 415 | mypy 9-element_length.py 416 | ``` 417 | 418 | ### :heavy_check_mark: Solution 419 | > [:point_right: 9-main.py](9-main.py) 420 | 421 | 422 | ## [10. Duck typing - first element of a sequence](100-basic_async_syntax.py) 423 | ### :page_with_curl: Task requirements. 424 | Augment the following code with the correct duck-typed annotations: 425 | ``` 426 | # The types of the elements of the input are not know 427 | def safe_first_element(lst): 428 | if lst: 429 | return lst[0] 430 | else: 431 | return None 432 | ``` 433 | 434 | ``` 435 | bob@dylan:~$ cat 100-basic_async_syntax.py 436 | #!/usr/bin/env python3 437 | 438 | safe_first_element = __import__('100-safe_first_element').safe_first_element 439 | 440 | print(safe_first_element.__annotations__) 441 | 442 | bob@dylan:~$ ./100-basic_async_syntax.py 443 | {'lst': typing.Sequence[typing.Any], 'return': typing.Union[typing.Any, NoneType]} 444 | ``` 445 | 446 | ### :wrench: Task setup. 447 | ```bash 448 | # Create task files and set execute permission. 449 | touch 100-safe_first_element.py 450 | chmod +x 100-safe_first_element.py 451 | ./100-safe_first_element.py 452 | 453 | # Tests 454 | touch 100-basic_async_syntax.py 455 | chmod +x 100-basic_async_syntax.py 456 | ./100-basic_async_syntax.py 457 | 458 | # Lint 459 | pycodestyle 100-safe_first_element.py 460 | mypy 100-safe_first_element.py 461 | ``` 462 | 463 | ### :heavy_check_mark: Solution 464 | > [:point_right: 100-basic_async_syntax.py](100-basic_async_syntax.py) 465 | 466 | 467 | ## [11. More involved type annotations](101-main.py) 468 | ### :page_with_curl: Task requirements. 469 | Given the parameters and the return values, add type annotations to the function 470 | 471 | Hint: look into TypeVar 472 | ``` 473 | def safely_get_value(dct, key, default = None): 474 | if key in dct: 475 | return dct[key] 476 | else: 477 | return default 478 | ``` 479 | 480 | ``` 481 | bob@dylan:~$ cat 101-main.py 482 | #!/usr/bin/env python3 483 | 484 | safely_get_value = __import__('101-safely_get_value').safely_get_value 485 | annotations = safely_get_value.__annotations__ 486 | 487 | print("Here's what the mappings should look like") 488 | for k, v in annotations.items(): 489 | print( ("{}: {}".format(k, v))) 490 | 491 | bob@dylan:~$ ./101-main.py 492 | Here's what the mappings should look like 493 | dct: typing.Mapping 494 | key: typing.Any 495 | default: typing.Union[~T, NoneType] 496 | return: typing.Union[typing.Any, ~T] 497 | ``` 498 | 499 | ### :wrench: Task setup. 500 | ```bash 501 | # Create task files and set execute permission. 502 | touch 101-safely_get_value.py 503 | chmod +x 101-safely_get_value.py 504 | ./101-safely_get_value.py 505 | 506 | # Tests 507 | touch 101-main.py 508 | chmod +x 101-main.py 509 | ./101-main.py 510 | 511 | # Lint 512 | pycodestyle 101-safely_get_value.py 513 | mypy 101-safely_get_value.py 514 | ``` 515 | 516 | ### :heavy_check_mark: Solution 517 | > [:point_right: 101-main.py](101-main.py) 518 | 519 | 520 | ## [0. Basic annotations - add](102-measure_runtime.py) 521 | ### :page_with_curl: Task requirements. 522 | Write a type-annotated function add that takes a float a and a float b as arguments and returns their sum as a float. 523 | ``` 524 | bob@dylan:~$ cat 102-measure_runtime.py 525 | #!/usr/bin/env python3 526 | add = __import__('0-add').add 527 | 528 | print(add(1.11, 2.22) == 1.11 + 2.22) 529 | print(add.__annotations__) 530 | 531 | bob@dylan:~$ ./102-measure_runtime.py 532 | True 533 | {'a': , 'b': , 'return': } 534 | ``` 535 | 536 | ### :wrench: Task setup. 537 | ```bash 538 | # Create task files and set execute permission. 539 | touch 102-type_checking.py 540 | chmod +x 102-type_checking.py 541 | ./102-type_checking.py 542 | 543 | # Tests 544 | touch 102-measure_runtime.py 545 | chmod +x 102-measure_runtime.py 546 | ./102-measure_runtime.py 547 | 548 | # Lint 549 | pycodestyle 102-type_checking.py 550 | mypy 102-type_checking.py 551 | ``` 552 | 553 | ### :heavy_check_mark: Solution 554 | > [:point_right: 102-measure_runtime.py](102-measure_runtime.py) -------------------------------------------------------------------------------- /0x02-python_async_comprehension/0-async_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This module provides an asynchronous generator function that yields a 4 | random float between 0 and 10 after a one second delay for a total of 5 | 10 iterations. 6 | """ 7 | import asyncio 8 | import random 9 | from typing import Generator 10 | 11 | 12 | async def async_generator() -> Generator[float, None, None]: 13 | """ 14 | Asynchronous generator function that yields a random float between 0 and 10 15 | after a one second delay for a total of 10 iterations. 16 | 17 | Returns: 18 | Generator: Asynchronous generator object that can be used in an 19 | awaitable context. 20 | """ 21 | for _ in range(10): 22 | await asyncio.sleep(1) 23 | yield random.random() * 10 24 | -------------------------------------------------------------------------------- /0x02-python_async_comprehension/0-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | 5 | async_generator = __import__('0-async_generator').async_generator 6 | 7 | async def print_yielded_values(): 8 | result = [] 9 | async for i in async_generator(): 10 | result.append(i) 11 | print(result) 12 | 13 | asyncio.run(print_yielded_values()) 14 | -------------------------------------------------------------------------------- /0x02-python_async_comprehension/1-async_comprehension.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This module provides an asynchronous list comprehension that 4 | returns a list of floats generated by the async_generator function 5 | from the '0-async_generator' module. 6 | """ 7 | from typing import List 8 | from importlib import import_module as using 9 | async_generator = using('0-async_generator').async_generator 10 | 11 | 12 | async def async_comprehension() -> List[float]: 13 | """ 14 | Asynchronous list comprehension that returns a list of floats 15 | generated by the async_generator function. 16 | 17 | Returns: 18 | List[float]: a list of floats generated by the async_generator. 19 | """ 20 | return [num async for num in async_generator()] 21 | -------------------------------------------------------------------------------- /0x02-python_async_comprehension/1-main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import asyncio 4 | 5 | async_comprehension = __import__('1-async_comprehension').async_comprehension 6 | 7 | 8 | async def main(): 9 | print(await async_comprehension()) 10 | 11 | asyncio.run(main()) 12 | -------------------------------------------------------------------------------- /0x02-python_async_comprehension/2-main.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdftyousra/alx-backend-python/3c8cb80067c3aadb6d2d7349a8bb6ff5133cc519/0x02-python_async_comprehension/2-main.py -------------------------------------------------------------------------------- /0x02-python_async_comprehension/2-measure_runtime.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This module provides an asynchronous function that measures the runtime 4 | of running 4 instances of the async_comprehension function from the 5 | '1-async_comprehension' module 6 | """ 7 | import asyncio 8 | import time 9 | from importlib import import_module as using 10 | 11 | async_comprehension = using('1-async_comprehension').async_comprehension 12 | 13 | 14 | async def measure_runtime() -> float: 15 | """ 16 | Asynchronous function that measures and returns the runtime of 17 | running 4 instances of the async_comprehension function. 18 | 19 | Returns: 20 | float: The runtime of the async_comprehension function in seconds. 21 | """ 22 | start_time = time.time() 23 | await asyncio.gather(*(async_comprehension() for _ in range(4))) 24 | return time.time() - start_time 25 | -------------------------------------------------------------------------------- /0x02-python_async_comprehension/README.md: -------------------------------------------------------------------------------- 1 | # :book: 0x02. Python - Async Comprehension. 2 | ## :page_with_curl: Topics Covered. 3 | 1. Python - Async Comprehension. 4 | 5 | # :computer: Tasks. 6 | ## [0. Async Generator](0-async_generator.py) 7 | ### :page_with_curl: Task requirements. 8 | Write a coroutine called async_generator that takes no arguments. 9 | 10 | The coroutine will loop 10 times, each time asynchronously wait 1 second, then yield a random number between 0 and 10. Use the random module. 11 | 12 | ``` 13 | bob@dylan:~$ cat 0-main.py 14 | #!/usr/bin/env python3 15 | 16 | import asyncio 17 | 18 | async_generator = __import__('0-async_generator').async_generator 19 | 20 | async def print_yielded_values(): 21 | result = [] 22 | async for i in async_generator(): 23 | result.append(i) 24 | print(result) 25 | 26 | asyncio.run(print_yielded_values()) 27 | 28 | bob@dylan:~$ ./0-main.py 29 | [4.403136952967102, 6.9092712604587465, 6.293445466782645, 4.549663490048418, 4.1326571686139015, 9.99058525304903, 6.726734105473811, 9.84331704602206, 1.0067279479988345, 1.3783306401737838] 30 | ``` 31 | 32 | ### :wrench: Task setup. 33 | ```bash 34 | # Create task files and set execute permission. 35 | touch 0-async_generator.py 36 | chmod +x 0-async_generator.py 37 | ./0-async_generator.py 38 | 39 | # Lint checks 40 | pycodestyle 0-async_generator.py 41 | mypy 0-async_generator.py 42 | 43 | # Tests 44 | touch 0-main.py 45 | chmod +x 0-main.py 46 | ./0-main.py 47 | ``` 48 | 49 | ### :heavy_check_mark: Solution 50 | > [:point_right: 0-async_generator.py](0-async_generator.py) 51 | 52 | 53 | ## [1. Async Comprehensions](1-async_comprehension.py) 54 | ### :page_with_curl: Task requirements. 55 | Import async_generator from the previous task and then write a coroutine called async_comprehension that takes no arguments. 56 | 57 | The coroutine will collect 10 random numbers using an async comprehensing over async_generator, then return the 10 random numbers. 58 | ``` 59 | bob@dylan:~$ cat 1-main.py 60 | #!/usr/bin/env python3 61 | 62 | import asyncio 63 | 64 | async_comprehension = __import__('1-async_comprehension').async_comprehension 65 | 66 | 67 | async def main(): 68 | print(await async_comprehension()) 69 | 70 | asyncio.run(main()) 71 | 72 | bob@dylan:~$ ./1-main.py 73 | [9.861842105071727, 8.572355293354995, 1.7467182056248265, 4.0724372912858575, 0.5524750922145316, 8.084266576021555, 8.387128918690468, 1.5486451376520916, 7.713335177885325, 7.673533267041574] 74 | ``` 75 | 76 | ### :wrench: Task setup. 77 | ```bash 78 | # Create task files and set execute permission. 79 | touch 1-async_comprehension.py 80 | chmod +x 1-async_comprehension.py 81 | ./1-async_comprehension.py 82 | 83 | pycodestyle 1-async_comprehension.py 84 | mypy 1-async_comprehension.py 85 | 86 | # Tests 87 | touch 1-main.py 88 | chmod +x 1-main.py 89 | ./1-main.py 90 | ``` 91 | 92 | ### :heavy_check_mark: Solution 93 | > [:point_right: 1-async_comprehension.py](1-async_comprehension.py) 94 | 95 | 96 | ## [2. Run time for four parallel comprehensions](2-measure_runtime.py) 97 | ### :page_with_curl: Task requirements. 98 | 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. 99 | 100 | measure_runtime should measure the total runtime and return it. 101 | 102 | Notice that the total runtime is roughly 10 seconds, explain it to yourself. 103 | ``` 104 | bob@dylan:~$ cat 2-main.py 105 | #!/usr/bin/env python3 106 | 107 | import asyncio 108 | 109 | 110 | measure_runtime = __import__('2-measure_runtime').measure_runtime 111 | 112 | 113 | async def main(): 114 | return await(measure_runtime()) 115 | 116 | print( 117 | asyncio.run(main()) 118 | ) 119 | 120 | bob@dylan:~$ ./2-main.py 121 | 10.021936893463135 122 | ``` 123 | 124 | ### :wrench: Task setup. 125 | ```bash 126 | # Create task files and set execute permission. 127 | touch 2-measure_runtime.py 128 | chmod +x 2-measure_runtime.py 129 | ./2-measure_runtime.py 130 | 131 | pycodestyle 2-measure_runtime.py 132 | mypy 2-measure_runtime.py 133 | 134 | # Tests 135 | touch 2-main.py 136 | chmod +x 2-main.py 137 | ./2-main.py 138 | ``` 139 | 140 | ### :heavy_check_mark: Solution 141 | > [:point_right: 2-measure_runtime.py](2-measure_runtime.py) 142 | 143 | # :man: Author and Credits. 144 | This project was done by [SE. Moses Mwangi](https://github.com/MosesSoftEng). Feel free to get intouch with me; 145 | 146 | :iphone: WhatsApp [+254115227963](https://wa.me/254115227963) 147 | 148 | :email: Email [moses.soft.eng@gmail.com](mailto:moses.soft.eng@gmail.com) 149 | 150 | :thumbsup: A lot of thanks to [ALX-Africa Software Engineering](https://www.alxafrica.com/) program for the project requirements. 151 | -------------------------------------------------------------------------------- /0x03-Unittests_and_integration_tests/README.md: -------------------------------------------------------------------------------- 1 | # Unittests and Integration Tests 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alx-backend-python --------------------------------------------------------------------------------