From d83224d8e24d9f296f7744e0a8153526641acb00 Mon Sep 17 00:00:00 2001 From: malikabaguari Date: Fri, 26 Jun 2026 21:18:47 +0200 Subject: [PATCH] Add File Week2_Lab Solution of the exercices regarding the lab python handling error --- lab-python-error-handling.ipynb | 26813 +++++++++++++++++++++++++++++- 1 file changed, 26718 insertions(+), 95 deletions(-) diff --git a/lab-python-error-handling.ipynb b/lab-python-error-handling.ipynb index f4c6ef6..11ee7c7 100644 --- a/lab-python-error-handling.ipynb +++ b/lab-python-error-handling.ipynb @@ -1,98 +1,26721 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "25d7736c-ba17-4aff-b6bb-66eba20fbf4e", - "metadata": {}, - "source": [ - "# Lab | Error Handling" - ] + + + +lab_error_ha… (15) - Try Jupyter! + + + + + + + + + + +
+
+
Loading JupyterLite...
+
+ + + +
Skip to main panel
  • File
  • Edit
  • View
  • Run
  • Kernel
  • Tabs
  • Settings
  • Help

Open Tabs
Close All

Kernels
Shut Down All

Recently Closed
Forget All

Workspaces
Delete All

default

Terminals
Shut Down All

lab_error_handling_exercices.ipynb

No Headings

The table of contents shows headings in notebooks and supported files.

/notebooks/Lab Week 2/
Name
ModifiedLast Modified
      [2]:
      # Lab 2
      # Product list

      products = ["t-shirt", "mug", "hat", "book", "keychain"]
      # Inventory dictionary
      inventory = {}

      # Ask for inventory quantities
      for product in products:
      quantity = int(input(f"Enter the quantity available for {product}: "))
      inventory[product] = quantity

      # Customer orders set
      customer_orders = set()
      Enter the quantity available for t-shirt:  10
      +Enter the quantity available for mug:  12
      +Enter the quantity available for hat:  10
      +Enter the quantity available for book:  15
      +Enter the quantity available for keychain:  11
      +
      [3]:
      # Loop for customer orders
      while True:
      product = input("Enter a product to order: ")

      if product in products:
      customer_orders.add(product)
      else:
      print("Product not available.")

      another = input("Do you want to add another product? (yes/no): ")

      if another.lower() != "yes":
      break
      Enter a product to order:  hat
      +Do you want to add another product? (yes/no):  no
      +
      [4]:
      # Display orders
      print("\nCustomer Orders:")
      print(customer_orders)
      +Customer Orders:
      +{'hat'}
      +
      [5]:
      # Order statistics
      total_products_ordered = len(customer_orders)
      percentage_ordered = (total_products_ordered / len(products)) * 100

      order_status = (total_products_ordered, percentage_ordered)

      print("\nOrder Statistics:")
      print(f"Total Products Ordered: {order_status[0]}")
      print(f"Percentage of Products Ordered: {order_status[1]}%")
      +Order Statistics:
      +Total Products Ordered: 1
      +Percentage of Products Ordered: 20.0%
      +
      [6]:
      # Update inventory only for ordered products
      for product in customer_orders:
      inventory[product] -= 1
      [7]:
      # Display updated inventory
      print("\nUpdated Inventory:")
      +Updated Inventory:
      +
      [8]:
      for product, quantity in inventory.items():
      print(f"{product}: {quantity}")
      t-shirt: 10
      +mug: 12
      +hat: 9
      +book: 15
      +keychain: 11
      +
      [ ]:

        [3]:
        #NUMPY

        import numpy as np # some important libraries ==> ALIAS numpy = np
        [ ]:

        [5]:
        weights_array = np.array( [0.3, 0.2, 0.5] )
        kanto_array = np.array( [73, 67, 43] )
        kanto_tield_array = sum(weights_array * kanto_array)
        print(kanto_tield_array)

        56.8
        +
        [ ]:
        kanto_tield_array2 = np.dot((weights_array, kanto_array)
        kanto_tield_array2
        [ ]:
        #CREATING ARRAY

        [6]:
        array_from_list = np.array([10,20,30,50,60])
        array_from_list
        [6]:
        array([10, 20, 30, 50, 60])
        [7]:
        range_array = np.arange(0,10,2)
        range_array
        [7]:
        array([0, 2, 4, 6, 8])
        [10]:
        zeros_array = np.zeros(5)
        zero_array
        [10]:
        array([0., 0., 0., 0., 0.])
        [11]:
        ones_array = np.ones(5)
        ones_array
        [11]:
        array([1., 1., 1., 1., 1.])
        [12]:
        ones_array_2d = np.ones( (3,4) ) #(3,4) --> row, column
        ones_array_2d
        [12]:
        array([[1., 1., 1., 1.],
        +       [1., 1., 1., 1.],
        +       [1., 1., 1., 1.]])
        [19]:
        sample_array = ([1, 2, 3, 4],
        [5, 6, 7, 8],
        [9, 10, 11, 12])
        sample_array
        [19]:
        ([1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12])
        [ ]:

        [ ]:

        [ ]:

        [ ]:
        #INDEXING

        [21]:
        numbers_1d = np.array([10, 20, 30, 40, 50])
        print(numbers_1d[0])
        print(numbers_1d[2])
        print(numbers_1d[-1])
        10
        +30
        +50
        +
        [22]:
        # slice (start : stop : step) --> stop is not included

        print(numbers_1d[1:4])
        [20 30 40]
        +
        [23]:
        print(type(numbers_1d))
        <class 'numpy.ndarray'>
        +
        [24]:
        print(numbers_1d[1:4])
        print(numbers_1d[:4])
        print(numbers_1d[2:])
        print(numbers_1d[::2])
        [20 30 40]
        +[10 20 30 40]
        +[30 40 50]
        +[10 30 50]
        +
        [ ]:

        [ ]:
        #depth, row, column
        print
        [ ]:

        [ ]:
        #pandas is also a library, designed to data anlysis and data
        [ ]:
        !pip install numpy pandas
        mambajs 0.19.13
        +
        +Process pip requirements ...
        +
        +Requirement numpy already handled by conda/micromamba/mamba.
        +
        Cannot install 'pandas' from PyPI because it is a binary built package that is not compatible with WASM environments. To resolve this issue, you can: 1) Try to install it from emscripten-forge instead: "!mamba install pandas" 2) If that doesn't work, it's probably that the package was not made WASM-compatible on emscripten-forge. You can either request or contribute a new recipe for that package in https://github.com/emscripten-forge/recipes 
        [29]:
        import numpy as np
        import pandas as pd #it is convention to write it like this

        ---------------------------------------------------------------------------
        +ModuleNotFoundError                       Traceback (most recent call last)
        +Cell In[29], line 2
        +      1 import numpy as np
        +----> 2 import pandas as pd   #it is convention to write it like this
        +
        +ModuleNotFoundError: No module named 'pandas'
        [ ]:

        [ ]:
        # PANDA DATAFRAME
        [ ]:
        titanic_dt_fr = pd.read_csv("titanic
          [ ]:
          # Data Cleaning
          [ ]:

          [ ]:
          # Data manipulation

          [ ]:
          # map === enter something, apply a transformation , then print it ---> series
          # apply === enters something, apply a transformation, then print it
          [ ]:

          [ ]:
          # filter
            [ ]:
            # Working with dates
            [ ]:
            #01-05-2026 =
            Notebook
            Python 3.13 (XPython)
            Kernel status: Idle
              [ ]:
              # Webscrapping _ Data Wrangling & Retrieval – End of Module 1 Project _ EV spot charges
              [ ]:
              # Our company want to identifies the region/ localisation in Spain where the installation of new electric charges are more profitable.
              We will collect electric vehicle sales data published on specialized websites in order to measure:

              market growth

              dominant brands

              best‑selling models

              This information will help anticipate future demand for charging stations.

              We will collect electric vehicle sales data published on specialized websites in order to measure:

              +

              market growth

              +

              dominant brands

              +

              best‑selling models

              +

              This information will help anticipate future demand for charging stations.

              +
              [ ]:

              [6]:
              !mamba install pandas
              mambajs 0.21.1
              +
              +Specs: xeus-python, numpy, matplotlib, pillow, ipywidgets>=8.1.6, ipyleaflet, scipy, pandas
              +Channels: emscripten-forge-4x, conda-forge
              +
              +Solving environment...
              +Solving took 1.1765 seconds
              +  Name           Version  Build                Channel
              +--------------------------------------------------------------------
              ++ pandas         3.0.3    np23py313h1e705a5_0  emscripten-forge-4x
              ++ python-tzdata  2026.2   pyhd8ed1ab_0         conda-forge
              +- pip            26.1.2   pyh145f28c_0         conda-forge
              +
              [7]:
              !pip install requests beautifulsoup4 pandas
              mambajs 0.21.1
              +
              +Process pip requirements ...
              +
              +Requirement typing-extensions already satisfied.
              +Requirement pandas already handled by conda/micromamba/mamba.
              +Successfully installed requests-2.34.2 charset_normalizer-3.4.7 idna-3.18 urllib3-2.7.0 certifi-2026.6.17 beautifulsoup4-4.15.0 soupsieve-2.8.4
              +
              [8]:
              import requests
              from bs4 import BeautifulSoup
              import pandas as pd
              [13]:
              import platform

              print(platform.platform())
              Emscripten-4.0.9-wasm32-32bit
              +
              [ ]:
              # Select the url
              [9]:
              url = "https://www.anfac.com"
              [ ]:
              # User agent
              [10]:
              headers = {
              "User-Agent":
              "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
              }
              [ ]:
              # Do the request
              [12]:
              import requests

              response = requests.get("https://httpbin.org/get")
              print(response.status_code)
              ---------------------------------------------------------------------------
              +JsGenericError                            Traceback (most recent call last)
              +File /lib/python3.13/site-packages/urllib3/contrib/emscripten/fetch.py:527, in send_request(request)
              +    525         js_xhr.setRequestHeader(name, value)
              +--> 527 js_xhr.send(to_js(request.body))
              +    529 headers = dict(Parser().parsestr(js_xhr.getAllResponseHeaders()))
              +
              +File /lib/python3.13/site-packages/pyjs/extend_js_val.py:44, in extend_val.<locals>.<lambda>(self, *args)
              +     43     return future
              +---> 44 JsValue.__call__ = lambda self, *args: apply(self, args=args)
              +     45 JsValue._asstr_unsafe = lambda self: pyjs_core.internal.to_string(self)
              +
              +File /lib/python3.13/site-packages/pyjs/core.py:234, in apply(js_function, args)
              +    233 js_array_args, is_generated_proxy = _make_js_args(args)
              +--> 234 ret, meta = pyjs_core.internal.apply_try_catch(js_function, js_array_args, is_generated_proxy)
              +    235 return ret
              +
              +File /lib/python3.13/site-packages/pyjs/convert.py:251, in error_to_py_and_raise(err)
              +    250 def error_to_py_and_raise(err):
              +--> 251     raise error_to_py(err)
              +
              +JsGenericError: {"stack":"NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'https://httpbin.org/get'.\n    at Module._apply_try_catch (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:1950)\n    at methodCaller<(emscripten::val, emscripten::val, emscripten::val) => emscripten::val> (eval at __emval_get_method_caller (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:222664), <anonymous>:7:17)\n    at __emval_call (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:220678)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17013]:0x6e353e\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17012]:0x6e3419\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[16940]:0x6dd1df\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[270]:0x21eaf6\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[18805]:0x78f3fc\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)"}
              +
              +During handling of the above exception, another exception occurred:
              +
              +_RequestError                             Traceback (most recent call last)
              +File /lib/python3.13/site-packages/urllib3/contrib/emscripten/connection.py:123, in EmscriptenHTTPConnection.request(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)
              +    122     if self._response is None:
              +--> 123         self._response = send_request(request)
              +    124 except _TimeoutError as e:
              +
              +File /lib/python3.13/site-packages/urllib3/contrib/emscripten/fetch.py:542, in send_request(request)
              +    541 elif err.name == "NetworkError":
              +--> 542     raise _RequestError(err.message, request=request)
              +    543 else:
              +    544     # general http error
              +
              +_RequestError: {"stack":"NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'https://httpbin.org/get'.\n    at Module._apply_try_catch (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:1950)\n    at methodCaller<(emscripten::val, emscripten::val, emscripten::val) => emscripten::val> (eval at __emval_get_method_caller (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:222664), <anonymous>:7:17)\n    at __emval_call (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:220678)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17013]:0x6e353e\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17012]:0x6e3419\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[16940]:0x6dd1df\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[270]:0x21eaf6\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[18805]:0x78f3fc\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)"}
              +
              +The above exception was the direct cause of the following exception:
              +
              +HTTPException                             Traceback (most recent call last)
              +File /lib/python3.13/site-packages/urllib3/connectionpool.py:788, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
              +    787 # Make the request on the HTTPConnection object
              +--> 788 response = self._make_request(
              +    789     conn,
              +    790     method,
              +    791     url,
              +    792     timeout=timeout_obj,
              +    793     body=body,
              +    794     headers=headers,
              +    795     chunked=chunked,
              +    796     retries=retries,
              +    797     response_conn=response_conn,
              +    798     preload_content=preload_content,
              +    799     decode_content=decode_content,
              +    800     **response_kw,
              +    801 )
              +    803 # Everything went great!
              +
              +File /lib/python3.13/site-packages/urllib3/connectionpool.py:493, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
              +    492 try:
              +--> 493     conn.request(
              +    494         method,
              +    495         url,
              +    496         body=body,
              +    497         headers=headers,
              +    498         chunked=chunked,
              +    499         preload_content=preload_content,
              +    500         decode_content=decode_content,
              +    501         enforce_content_length=enforce_content_length,
              +    502     )
              +    504 # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
              +    505 # legitimately able to close the connection after sending a valid response.
              +    506 # With this behaviour, the received response is still readable.
              +
              +File /lib/python3.13/site-packages/urllib3/contrib/emscripten/connection.py:127, in EmscriptenHTTPConnection.request(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)
              +    126 except _RequestError as e:
              +--> 127     raise HTTPException(e.message) from e
              +
              +HTTPException: {"stack":"NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'https://httpbin.org/get'.\n    at Module._apply_try_catch (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:1950)\n    at methodCaller<(emscripten::val, emscripten::val, emscripten::val) => emscripten::val> (eval at __emval_get_method_caller (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:222664), <anonymous>:7:17)\n    at __emval_call (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:220678)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17013]:0x6e353e\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17012]:0x6e3419\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[16940]:0x6dd1df\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[270]:0x21eaf6\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[18805]:0x78f3fc\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)"}
              +
              +During handling of the above exception, another exception occurred:
              +
              +ProtocolError                             Traceback (most recent call last)
              +File /lib/python3.13/site-packages/requests/adapters.py:696, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
              +    695 try:
              +--> 696     resp = conn.urlopen(
              +    697         method=request.method,
              +    698         url=url,
              +    699         body=request.body,  # type: ignore[arg-type]  # urllib3 stubs don't accept Iterable[bytes | str]
              +    700         headers=request.headers,  # type: ignore[arg-type]  # urllib3#3072
              +    701         redirect=False,
              +    702         assert_same_host=False,
              +    703         preload_content=False,
              +    704         decode_content=False,
              +    705         retries=self.max_retries,
              +    706         timeout=resolved_timeout,
              +    707         chunked=chunked,
              +    708     )
              +    710 except (ProtocolError, OSError) as err:
              +
              +File /lib/python3.13/site-packages/urllib3/connectionpool.py:842, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
              +    840     new_e = ProtocolError("Connection aborted.", new_e)
              +--> 842 retries = retries.increment(
              +    843     method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
              +    844 )
              +    845 retries.sleep()
              +
              +File /lib/python3.13/site-packages/urllib3/util/retry.py:498, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
              +    497 if read is False or method is None or not self._is_method_retryable(method):
              +--> 498     raise reraise(type(error), error, _stacktrace)
              +    499 elif read is not None:
              +
              +File /lib/python3.13/site-packages/urllib3/util/util.py:38, in reraise(tp, value, tb)
              +     37 if value.__traceback__ is not tb:
              +---> 38     raise value.with_traceback(tb)
              +     39 raise value
              +
              +File /lib/python3.13/site-packages/urllib3/connectionpool.py:788, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
              +    787 # Make the request on the HTTPConnection object
              +--> 788 response = self._make_request(
              +    789     conn,
              +    790     method,
              +    791     url,
              +    792     timeout=timeout_obj,
              +    793     body=body,
              +    794     headers=headers,
              +    795     chunked=chunked,
              +    796     retries=retries,
              +    797     response_conn=response_conn,
              +    798     preload_content=preload_content,
              +    799     decode_content=decode_content,
              +    800     **response_kw,
              +    801 )
              +    803 # Everything went great!
              +
              +File /lib/python3.13/site-packages/urllib3/connectionpool.py:493, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
              +    492 try:
              +--> 493     conn.request(
              +    494         method,
              +    495         url,
              +    496         body=body,
              +    497         headers=headers,
              +    498         chunked=chunked,
              +    499         preload_content=preload_content,
              +    500         decode_content=decode_content,
              +    501         enforce_content_length=enforce_content_length,
              +    502     )
              +    504 # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
              +    505 # legitimately able to close the connection after sending a valid response.
              +    506 # With this behaviour, the received response is still readable.
              +
              +File /lib/python3.13/site-packages/urllib3/contrib/emscripten/connection.py:127, in EmscriptenHTTPConnection.request(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)
              +    126 except _RequestError as e:
              +--> 127     raise HTTPException(e.message) from e
              +
              +ProtocolError: ('Connection aborted.', HTTPException('{"stack":"NetworkError: Failed to execute \'send\' on \'XMLHttpRequest\': Failed to load \'https://httpbin.org/get\'.\\n    at Module._apply_try_catch (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:1950)\\n    at methodCaller<(emscripten::val, emscripten::val, emscripten::val) => emscripten::val> (eval at __emval_get_method_caller (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:222664), <anonymous>:7:17)\\n    at __emval_call (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:220678)\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17013]:0x6e353e\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17012]:0x6e3419\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[16940]:0x6dd1df\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[270]:0x21eaf6\\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[18805]:0x78f3fc\\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)"}'))
              +
              +During handling of the above exception, another exception occurred:
              +
              +ConnectionError                           Traceback (most recent call last)
              +Cell In[10], line 3
              +      1 import requests
              +----> 3 response = requests.get("https://httpbin.org/get")
              +      4 print(response.status_code)
              +
              +File /lib/python3.13/site-packages/requests/api.py:87, in get(url, params, **kwargs)
              +     74 def get(
              +     75     url: _t.UriType, params: _t.ParamsType = None, **kwargs: Unpack[_t.GetKwargs]
              +     76 ) -> Response:
              +     77     r"""Sends a GET request.
              +     78 
              +     79     :param url: URL for the new :class:`Request` object.
              +   (...)     84     :rtype: requests.Response
              +     85     """
              +---> 87     return request("get", url, params=params, **kwargs)
              +
              +File /lib/python3.13/site-packages/requests/api.py:71, in request(method, url, **kwargs)
              +     67 # By using the 'with' statement we are sure the session is closed, thus we
              +     68 # avoid leaving sockets open which can trigger a ResourceWarning in some
              +     69 # cases, and look like a memory leak in others.
              +     70 with sessions.Session() as session:
              +---> 71     return session.request(method=method, url=url, **kwargs)
              +
              +File /lib/python3.13/site-packages/requests/sessions.py:651, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
              +    646 send_kwargs = {
              +    647     "timeout": timeout,
              +    648     "allow_redirects": allow_redirects,
              +    649 }
              +    650 send_kwargs.update(settings)
              +--> 651 resp = self.send(prep, **send_kwargs)
              +    653 return resp
              +
              +File /lib/python3.13/site-packages/requests/sessions.py:784, in Session.send(self, request, **kwargs)
              +    781 start = preferred_clock()
              +    783 # Send the request
              +--> 784 r = adapter.send(request, **kwargs)
              +    786 # Total elapsed time of the request (approximately)
              +    787 elapsed = preferred_clock() - start
              +
              +File /lib/python3.13/site-packages/requests/adapters.py:711, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
              +    696     resp = conn.urlopen(
              +    697         method=request.method,
              +    698         url=url,
              +   (...)    707         chunked=chunked,
              +    708     )
              +    710 except (ProtocolError, OSError) as err:
              +--> 711     raise ConnectionError(err, request=request)
              +    713 except MaxRetryError as e:
              +    714     if isinstance(e.reason, ConnectTimeoutError):
              +    715         # TODO: Remove this in 3.0.0: see #2811
              +
              +ConnectionError: ('Connection aborted.', HTTPException('{"stack":"NetworkError: Failed to execute \'send\' on \'XMLHttpRequest\': Failed to load \'https://httpbin.org/get\'.\\n    at Module._apply_try_catch (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:1950)\\n    at methodCaller<(emscripten::val, emscripten::val, emscripten::val) => emscripten::val> (eval at __emval_get_method_caller (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:222664), <anonymous>:7:17)\\n    at __emval_call (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:220678)\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17013]:0x6e353e\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17012]:0x6e3419\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[16940]:0x6dd1df\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[270]:0x21eaf6\\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[18805]:0x78f3fc\\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)"}'))
              [11]:
              response = requests.get(
              url,
              headers=headers
              )

              print(response.status_code)
              ---------------------------------------------------------------------------
              +JsGenericError                            Traceback (most recent call last)
              +File /lib/python3.13/site-packages/urllib3/contrib/emscripten/fetch.py:527, in send_request(request)
              +    525         js_xhr.setRequestHeader(name, value)
              +--> 527 js_xhr.send(to_js(request.body))
              +    529 headers = dict(Parser().parsestr(js_xhr.getAllResponseHeaders()))
              +
              +File /lib/python3.13/site-packages/pyjs/extend_js_val.py:44, in extend_val.<locals>.<lambda>(self, *args)
              +     43     return future
              +---> 44 JsValue.__call__ = lambda self, *args: apply(self, args=args)
              +     45 JsValue._asstr_unsafe = lambda self: pyjs_core.internal.to_string(self)
              +
              +File /lib/python3.13/site-packages/pyjs/core.py:234, in apply(js_function, args)
              +    233 js_array_args, is_generated_proxy = _make_js_args(args)
              +--> 234 ret, meta = pyjs_core.internal.apply_try_catch(js_function, js_array_args, is_generated_proxy)
              +    235 return ret
              +
              +File /lib/python3.13/site-packages/pyjs/convert.py:251, in error_to_py_and_raise(err)
              +    250 def error_to_py_and_raise(err):
              +--> 251     raise error_to_py(err)
              +
              +JsGenericError: {"stack":"NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'https://www.anfac.com/'.\n    at Module._apply_try_catch (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:1950)\n    at methodCaller<(emscripten::val, emscripten::val, emscripten::val) => emscripten::val> (eval at __emval_get_method_caller (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:222664), <anonymous>:7:17)\n    at __emval_call (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:220678)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17013]:0x6e353e\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17012]:0x6e3419\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[16940]:0x6dd1df\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[270]:0x21eaf6\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[18805]:0x78f3fc\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)"}
              +
              +During handling of the above exception, another exception occurred:
              +
              +_RequestError                             Traceback (most recent call last)
              +File /lib/python3.13/site-packages/urllib3/contrib/emscripten/connection.py:123, in EmscriptenHTTPConnection.request(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)
              +    122     if self._response is None:
              +--> 123         self._response = send_request(request)
              +    124 except _TimeoutError as e:
              +
              +File /lib/python3.13/site-packages/urllib3/contrib/emscripten/fetch.py:542, in send_request(request)
              +    541 elif err.name == "NetworkError":
              +--> 542     raise _RequestError(err.message, request=request)
              +    543 else:
              +    544     # general http error
              +
              +_RequestError: {"stack":"NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'https://www.anfac.com/'.\n    at Module._apply_try_catch (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:1950)\n    at methodCaller<(emscripten::val, emscripten::val, emscripten::val) => emscripten::val> (eval at __emval_get_method_caller (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:222664), <anonymous>:7:17)\n    at __emval_call (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:220678)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17013]:0x6e353e\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17012]:0x6e3419\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[16940]:0x6dd1df\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[270]:0x21eaf6\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[18805]:0x78f3fc\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)"}
              +
              +The above exception was the direct cause of the following exception:
              +
              +HTTPException                             Traceback (most recent call last)
              +File /lib/python3.13/site-packages/urllib3/connectionpool.py:788, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
              +    787 # Make the request on the HTTPConnection object
              +--> 788 response = self._make_request(
              +    789     conn,
              +    790     method,
              +    791     url,
              +    792     timeout=timeout_obj,
              +    793     body=body,
              +    794     headers=headers,
              +    795     chunked=chunked,
              +    796     retries=retries,
              +    797     response_conn=response_conn,
              +    798     preload_content=preload_content,
              +    799     decode_content=decode_content,
              +    800     **response_kw,
              +    801 )
              +    803 # Everything went great!
              +
              +File /lib/python3.13/site-packages/urllib3/connectionpool.py:493, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
              +    492 try:
              +--> 493     conn.request(
              +    494         method,
              +    495         url,
              +    496         body=body,
              +    497         headers=headers,
              +    498         chunked=chunked,
              +    499         preload_content=preload_content,
              +    500         decode_content=decode_content,
              +    501         enforce_content_length=enforce_content_length,
              +    502     )
              +    504 # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
              +    505 # legitimately able to close the connection after sending a valid response.
              +    506 # With this behaviour, the received response is still readable.
              +
              +File /lib/python3.13/site-packages/urllib3/contrib/emscripten/connection.py:127, in EmscriptenHTTPConnection.request(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)
              +    126 except _RequestError as e:
              +--> 127     raise HTTPException(e.message) from e
              +
              +HTTPException: {"stack":"NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'https://www.anfac.com/'.\n    at Module._apply_try_catch (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:1950)\n    at methodCaller<(emscripten::val, emscripten::val, emscripten::val) => emscripten::val> (eval at __emval_get_method_caller (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:222664), <anonymous>:7:17)\n    at __emval_call (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:220678)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17013]:0x6e353e\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17012]:0x6e3419\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[16940]:0x6dd1df\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[270]:0x21eaf6\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[18805]:0x78f3fc\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)"}
              +
              +During handling of the above exception, another exception occurred:
              +
              +ProtocolError                             Traceback (most recent call last)
              +File /lib/python3.13/site-packages/requests/adapters.py:696, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
              +    695 try:
              +--> 696     resp = conn.urlopen(
              +    697         method=request.method,
              +    698         url=url,
              +    699         body=request.body,  # type: ignore[arg-type]  # urllib3 stubs don't accept Iterable[bytes | str]
              +    700         headers=request.headers,  # type: ignore[arg-type]  # urllib3#3072
              +    701         redirect=False,
              +    702         assert_same_host=False,
              +    703         preload_content=False,
              +    704         decode_content=False,
              +    705         retries=self.max_retries,
              +    706         timeout=resolved_timeout,
              +    707         chunked=chunked,
              +    708     )
              +    710 except (ProtocolError, OSError) as err:
              +
              +File /lib/python3.13/site-packages/urllib3/connectionpool.py:842, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
              +    840     new_e = ProtocolError("Connection aborted.", new_e)
              +--> 842 retries = retries.increment(
              +    843     method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2]
              +    844 )
              +    845 retries.sleep()
              +
              +File /lib/python3.13/site-packages/urllib3/util/retry.py:498, in Retry.increment(self, method, url, response, error, _pool, _stacktrace)
              +    497 if read is False or method is None or not self._is_method_retryable(method):
              +--> 498     raise reraise(type(error), error, _stacktrace)
              +    499 elif read is not None:
              +
              +File /lib/python3.13/site-packages/urllib3/util/util.py:38, in reraise(tp, value, tb)
              +     37 if value.__traceback__ is not tb:
              +---> 38     raise value.with_traceback(tb)
              +     39 raise value
              +
              +File /lib/python3.13/site-packages/urllib3/connectionpool.py:788, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
              +    787 # Make the request on the HTTPConnection object
              +--> 788 response = self._make_request(
              +    789     conn,
              +    790     method,
              +    791     url,
              +    792     timeout=timeout_obj,
              +    793     body=body,
              +    794     headers=headers,
              +    795     chunked=chunked,
              +    796     retries=retries,
              +    797     response_conn=response_conn,
              +    798     preload_content=preload_content,
              +    799     decode_content=decode_content,
              +    800     **response_kw,
              +    801 )
              +    803 # Everything went great!
              +
              +File /lib/python3.13/site-packages/urllib3/connectionpool.py:493, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
              +    492 try:
              +--> 493     conn.request(
              +    494         method,
              +    495         url,
              +    496         body=body,
              +    497         headers=headers,
              +    498         chunked=chunked,
              +    499         preload_content=preload_content,
              +    500         decode_content=decode_content,
              +    501         enforce_content_length=enforce_content_length,
              +    502     )
              +    504 # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
              +    505 # legitimately able to close the connection after sending a valid response.
              +    506 # With this behaviour, the received response is still readable.
              +
              +File /lib/python3.13/site-packages/urllib3/contrib/emscripten/connection.py:127, in EmscriptenHTTPConnection.request(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)
              +    126 except _RequestError as e:
              +--> 127     raise HTTPException(e.message) from e
              +
              +ProtocolError: ('Connection aborted.', HTTPException('{"stack":"NetworkError: Failed to execute \'send\' on \'XMLHttpRequest\': Failed to load \'https://www.anfac.com/\'.\\n    at Module._apply_try_catch (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:1950)\\n    at methodCaller<(emscripten::val, emscripten::val, emscripten::val) => emscripten::val> (eval at __emval_get_method_caller (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:222664), <anonymous>:7:17)\\n    at __emval_call (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:220678)\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17013]:0x6e353e\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17012]:0x6e3419\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[16940]:0x6dd1df\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[270]:0x21eaf6\\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[18805]:0x78f3fc\\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)"}'))
              +
              +During handling of the above exception, another exception occurred:
              +
              +ConnectionError                           Traceback (most recent call last)
              +Cell In[9], line 1
              +----> 1 response = requests.get(
              +      2     url,
              +      3     headers=headers
              +      4 )
              +      6 print(response.status_code)
              +
              +File /lib/python3.13/site-packages/requests/api.py:87, in get(url, params, **kwargs)
              +     74 def get(
              +     75     url: _t.UriType, params: _t.ParamsType = None, **kwargs: Unpack[_t.GetKwargs]
              +     76 ) -> Response:
              +     77     r"""Sends a GET request.
              +     78 
              +     79     :param url: URL for the new :class:`Request` object.
              +   (...)     84     :rtype: requests.Response
              +     85     """
              +---> 87     return request("get", url, params=params, **kwargs)
              +
              +File /lib/python3.13/site-packages/requests/api.py:71, in request(method, url, **kwargs)
              +     67 # By using the 'with' statement we are sure the session is closed, thus we
              +     68 # avoid leaving sockets open which can trigger a ResourceWarning in some
              +     69 # cases, and look like a memory leak in others.
              +     70 with sessions.Session() as session:
              +---> 71     return session.request(method=method, url=url, **kwargs)
              +
              +File /lib/python3.13/site-packages/requests/sessions.py:651, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
              +    646 send_kwargs = {
              +    647     "timeout": timeout,
              +    648     "allow_redirects": allow_redirects,
              +    649 }
              +    650 send_kwargs.update(settings)
              +--> 651 resp = self.send(prep, **send_kwargs)
              +    653 return resp
              +
              +File /lib/python3.13/site-packages/requests/sessions.py:784, in Session.send(self, request, **kwargs)
              +    781 start = preferred_clock()
              +    783 # Send the request
              +--> 784 r = adapter.send(request, **kwargs)
              +    786 # Total elapsed time of the request (approximately)
              +    787 elapsed = preferred_clock() - start
              +
              +File /lib/python3.13/site-packages/requests/adapters.py:711, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
              +    696     resp = conn.urlopen(
              +    697         method=request.method,
              +    698         url=url,
              +   (...)    707         chunked=chunked,
              +    708     )
              +    710 except (ProtocolError, OSError) as err:
              +--> 711     raise ConnectionError(err, request=request)
              +    713 except MaxRetryError as e:
              +    714     if isinstance(e.reason, ConnectTimeoutError):
              +    715         # TODO: Remove this in 3.0.0: see #2811
              +
              +ConnectionError: ('Connection aborted.', HTTPException('{"stack":"NetworkError: Failed to execute \'send\' on \'XMLHttpRequest\': Failed to load \'https://www.anfac.com/\'.\\n    at Module._apply_try_catch (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:1950)\\n    at methodCaller<(emscripten::val, emscripten::val, emscripten::val) => emscripten::val> (eval at __emval_get_method_caller (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:222664), <anonymous>:7:17)\\n    at __emval_call (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:220678)\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17013]:0x6e353e\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[17012]:0x6e3419\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[16940]:0x6dd1df\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[270]:0x21eaf6\\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)\\n    at https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.wasm:wasm-function[18805]:0x78f3fc\\n    at _PyEM_TrampolineCall_JavaScript (https://jupyter.org/try-jupyter/xeus/xeus-python-kernel/bin/xpython.js:7:628185)"}'))
              [ ]:
              # Parse the request
              [ ]:
              from bs4 import BeautifulSoup

              soup = BeautifulSoup(
              response.content,
              "html.parser"
              )
              [ ]:
              # 2 . Explore the HTML Structure
              [ ]:
              all_divs = soup.find_all("div")

              print(len(all_divs))
              [14]:
              for div in all_divs:
              print(div.get("class"))
              ---------------------------------------------------------------------------
              +NameError                                 Traceback (most recent call last)
              +Cell In[12], line 1
              +----> 1 for div in all_divs:
              +      2     print(div.get("class"))
              +
              +NameError: name 'all_divs' is not defined
              [ ]:

                [17]:
                #1 Define a list called products
                products = ["t-shirt", "mug", "hat", "book", "keychain"]
                [18]:
                #2.Create an empty dictionary called inventory
                inventory_dict= {}

                [19]:
                #3.Ask the user to input the quantity of each product available in the inventory
                for product in products :
                quantity = input("How many do you need? : ")
                quantity = int(quantity)
                inventory_dict [ product] = quantity

                print(inventory_dict)
                How many do you need? :  2
                +How many do you need? :  3
                +How many do you need? :  4
                +How many do you need? :  5
                +How many do you need? :  6
                +
                {'t-shirt': 2, 'mug': 3, 'hat': 4, 'book': 5, 'keychain': 6}
                +
                [21]:
                #4.Create an empty set called customer_orders
                customer_orders = set()

                #5 Ask the user to input the name of three products
                for p in range(3):
                product = input("select the product: ")
                customer_orders.add(product)





                select the product:  hat
                +select the product:  book
                +select the product:  mug
                +
                [22]:
                #6 Print the products in the customer_orders set
                print(customer_orders)

                {'hat', 'mug', 'book'}
                +
                [7]:
                #7 Calculate the statistics:

                total_product_ordered = len(customer_orders)
                rate_product_ordered = (total_product_ordered/len(products))*100
                order_status = (total_product_ordered, rate_product_ordered)
                print(order_status)
                (3, 60.0)
                +
                [23]:
                #8 Print the order statistics

                print("\nOrder Statistics:")
                print(f"Total Products Ordered: {order_status[0]}")
                print(f"Percentage of Products Ordered: {order_status[1]}%")
                +Order Statistics:
                +Total Products Ordered: 3
                +Percentage of Products Ordered: 60.0%
                +
                [24]:
                #9 Update the inventory by subtracting 1
                for x,y in inventory_dict.items():
                inventory_dict[x] = y - 1

                [25]:
                print(inventory_dict)

                {'t-shirt': 1, 'mug': 2, 'hat': 3, 'book': 4, 'keychain': 5}
                +
                [26]:
                # 10 Print the updated inventory
                for x,y in inventory_dict.items():
                print(x ,":", y)
                t-shirt : 1
                +mug : 2
                +hat : 3
                +book : 4
                +keychain : 5
                +
                [ ]:

                Notebook
                Python 3.13 (XPython)
                Kernel status: Idle
                  [ ]:
                  # Project Shark _ Data Frame Team
                  [3]:
                  import numpy as np
                  [4]:
                  !mamba install pandas

                  mambajs 0.21.1
                  +
                  +Specs: xeus-python, numpy, matplotlib, pillow, ipywidgets>=8.1.6, ipyleaflet, scipy, pandas
                  +Channels: emscripten-forge-4x, conda-forge
                  +
                  +Solving environment...
                  +Solving took 1.3468000000044704 seconds
                  +  Name           Version  Build                Channel
                  +--------------------------------------------------------------------
                  ++ pandas         3.0.3    np23py313h1e705a5_0  emscripten-forge-4x
                  ++ python-tzdata  2026.2   pyhd8ed1ab_0         conda-forge
                  +- pip            26.1.2   pyh145f28c_0         conda-forge
                  +
                  [5]:
                  import pandas as pd
                  [ ]:
                  # Analyze Shark Attacks in the world
                  [6]:
                  import os

                  print(os.getcwd())
                  /drive/notebooks
                  +
                  [7]:
                  import platform

                  print(platform.platform())
                  Emscripten-4.0.9-wasm32-32bit
                  +
                  [8]:
                  import os
                  print(os.getcwd())
                  /drive/notebooks
                  +
                  [9]:
                  !pip install xlrd
                  mambajs 0.21.1
                  +
                  +Process pip requirements ...
                  +
                  +Successfully installed xlrd-2.0.2
                  +
                  [14]:
                  import pandas as pd

                  df_shark = pd.read_excel("GSAF5.xls")
                  print(df_shark.head())
                           Date    Year          Type    Country              State  \
                  +0    24th May  2026.0    Unprovoked  Australia         Queensland   
                  +1    17th May  2026.0  Questionable        USA           Maryland   
                  +2    16th May  2026.0    Unprovoked  Australia  Western Australia   
                  +3  14th April  2026.0    UNprovoked   Maldives   Gaafu Alif Atoll   
                  +4   3rd April  2026.0    Unprovoked  Australia    South Australia   
                  +
                  +                                      Location      Activity  \
                  +0                                Kennedy Shoal  Spearfishing   
                  +1                        Assateague State Park       Surfing   
                  +2              Horseshoe Reef  Rottnest Island    Skindiving   
                  +3                                      Kooddoo      Swimming   
                  +4  Middleton Beach Fleurieu Peninsula Adelaide       Surfing   
                  +
                  +                        Name Sex Age  ...  \
                  +0              Michael Jensz   M  39  ...   
                  +1              Brendan Oster   M   ?  ...   
                  +2           Steven Mattaboni   M  38  ...   
                  +3  Not stated - on honeymoon   M   ?  ...   
                  +4       Oliver Tokic-Bensley   M  16  ...   
                  +
                  +                              Species   \
                  +0  Undetermined Bull shark most likely   
                  +1         Blue fish bite most probable   
                  +2                    Great White Shark   
                  +3                              Unknown   
                  +4                        Bronze Whaler   
                  +
                  +                                              Source  pdf href formula href  \
                  +0         Simon de Marchi: 9 News: 7 News: ABC News:  NaN          NaN  NaN   
                  +1                       Keithe Cowley: Delmarva Now:  NaN          NaN  NaN   
                  +2          ABC News: 9 News: 7 News: Simon De Marchi  NaN          NaN  NaN   
                  +3                      The U.S. Sun: Simon De Marchi  NaN          NaN  NaN   
                  +4  ABC News: The Guardian:  Andrew Currie and Bob...  NaN          NaN  NaN   
                  +
                  +  Case Number Case Number.1 original order Unnamed: 21 Unnamed: 22  
                  +0         NaN           NaN            NaN         NaN         NaN  
                  +1         NaN           NaN            NaN         NaN         NaN  
                  +2         NaN           NaN            NaN         NaN         NaN  
                  +3         NaN           NaN            NaN         NaN         NaN  
                  +4         NaN           NaN            NaN         NaN         NaN  
                  +
                  +[5 rows x 23 columns]
                  +
                  [16]:
                  print(type(df_shark))
                  print(df_shark.columns.tolist())
                  <class 'pandas.DataFrame'>
                  +['Date', 'Year', 'Type', 'Country', 'State', 'Location', 'Activity', 'Name', 'Sex', 'Age', 'Injury', 'Fatal Y/N', 'Time', 'Species ', 'Source', 'pdf', 'href formula', 'href', 'Case Number', 'Case Number.1', 'original order', 'Unnamed: 21', 'Unnamed: 22']
                  +
                  [17]:
                  # we clean the columns we selected
                  cols = ["Country", "State", "Location"]

                  for col in cols :
                  df_shark[col] = df_shark[col].astype(str).str.strip() # suppress the unecessary spaces in the text
                  df_shark[col] = df_shark[col].str.replace(r"[.,]+$", " ",regex=True) #suppress the comas or points at the end of the text
                  df_shark[col] = df_shark[col].replace(["nan", "NAN", "none", " "],np.nan) # Convert the nan
                  [19]:
                  df_shark[["Country", "State", "Location"]].head(20) # we verify the cleaning for the 20 first rows
                  [17]:
                  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                  CountryStateLocation
                  0AustraliaQueenslandKennedy Shoal
                  1USAMarylandAssateague State Park
                  2AustraliaWestern AustraliaHorseshoe Reef  Rottnest Island
                  3MaldivesGaafu Alif AtollKooddoo
                  4AustraliaSouth AustraliaMiddleton Beach Fleurieu Peninsula Adelaide
                  5BahamasAndros IslandFresh Creek
                  6AustraliaSouth AustraliaCape Jaffa Limestone Coast
                  7AustraliaNSWLittle Avalon Beach
                  8USACaliforniaBig River Beach Mendocino County
                  9AustraliaWestern AustraliaExmouth
                  10AustraliaWestern AustraliaExmouth
                  11AustraliaQueenslandLady Elliott Island
                  12New CaledoniaNoumeaAnse Vata near Point Magnin
                  13Cayman IslandsLittle Cayman IslandCaribbean Sea
                  14BrazilRecifeDel Chifre Beach in Olinda
                  15AustraliaNSWAngels Beach East Ballina
                  16AustraliaTasmaniaCooee Beach west of Burnie
                  17AustraliaNSWPoint Plomber North of Port Macquarie
                  18AustraliaNSWDee Why
                  19AustraliaNSWNorth Steyne
                  +
                  [21]:
                  # For the Country column we will upper the text
                  df_shark["Country"] = df_shark["Country"].str.upper()
                  [22]:
                  # we amend the variation
                  country_mapping = {
                  "USA" : "UNITED STATES",
                  "U.S.A" : "UNITED STATES",
                  "UNITED STATES OF AMERICA" : "UNITED STATES",
                  "AUSTRALIA" : "AUSTRALIA",
                  "ENGLAND" : "UNITED KINGDOM",
                  "SCOTLAND" : "UNITED KINGDOM",
                  "WALES" : "UNITED KINGDOM",
                  }
                  df_shark["Country"] = df_shark["Country"].replace(country_mapping)
                  [23]:
                  df_shark["Country"].value_counts().head(20)
                  [21]:
                  Country
                  +UNITED STATES       2581
                  +AUSTRALIA           1531
                  +SOUTH AFRICA         599
                  +NEW ZEALAND          146
                  +BAHAMAS              142
                  +PAPUA NEW GUINEA     136
                  +BRAZIL               123
                  +MEXICO               107
                  +ITALY                 72
                  +FIJI                  70
                  +NEW CALEDONIA         68
                  +PHILIPPINES           65
                  +REUNION               60
                  +EGYPT                 53
                  +MOZAMBIQUE            52
                  +CUBA                  49
                  +SPAIN                 47
                  +UNITED KINGDOM        44
                  +INDIA                 41
                  +FRENCH POLYNESIA      40
                  +Name: count, dtype: int64
                  [26]:
                  # We want to analyze the top 10 countries.
                  top_countries = (
                  df_shark["Country"]
                  .value_counts()
                  .head(10)
                  )
                  [27]:
                  top_countries.plot(
                  kind="bar"
                  ) # Question : In which countries do most attacks occur?
                  [25]:
                  <Axes: xlabel='Country'>
                  [29]:
                  # for the State column cleansing we start with harmonazing the format
                  df_shark["State"] = df_shark["State"].str.title()
                  [30]:
                  # We correct now any possible abbreviations.
                  state_mapping = {
                  "Nsw" : "New South Wales",
                  "Qld" : "Queensland",
                  "Wa" : "Western Australia",
                  "Sa" : "South Australia"
                  }
                  df_shark["State"] = df_shark["State"].replace(state_mapping)
                  [31]:
                  df_shark["State"].value_counts().head(20)

                  [29]:
                  State
                  +Florida                  1193
                  +New South Wales           539
                  +Queensland                356
                  +Hawaii                    345
                  +California                328
                  +Western Australia         247
                  +Kwazulu-Natal             219
                  +Western Cape Province     197
                  +South Carolina            176
                  +Eastern Cape Province     168
                  +South Australia           122
                  +North Carolina            122
                  +Victoria                   98
                  +Texas                      83
                  +Pernambuco                 80
                  +North Island               76
                  +Torres Strait              72
                  +New Jersey                 57
                  +New York                   48
                  +Tasmania                   45
                  +Name: count, dtype: int64
                  [35]:
                  import matplotlib.pyplot as plt

                  plt.figure(figsize=(12,6))

                  state_counts.sort_values().plot(
                  kind="barh"
                  )
                  plt.title("top 20 States with Most Shark Attacks")
                  plt.xlabel("Number of Attacks")

                  plt.show()
                  [36]:
                  # Analyze the percentage represented for each region :

                  state_percent = (
                  df_shark["State"]
                  .value_counts(normalize=True)
                  .mul(100)
                  .head(20)
                  )

                  state_percent.round(2)
                  [34]:
                  State
                  +Florida                  18.07
                  +New South Wales           8.16
                  +Queensland                5.39
                  +Hawaii                    5.22
                  +California                4.97
                  +Western Australia         3.74
                  +Kwazulu-Natal             3.32
                  +Western Cape Province     2.98
                  +South Carolina            2.67
                  +Eastern Cape Province     2.54
                  +South Australia           1.85
                  +North Carolina            1.85
                  +Victoria                  1.48
                  +Texas                     1.26
                  +Pernambuco                1.21
                  +North Island              1.15
                  +Torres Strait             1.09
                  +New Jersey                0.86
                  +New York                  0.73
                  +Tasmania                  0.68
                  +Name: proportion, dtype: float64
                  [ ]:
                  # Analyze which region has the most fatal attack.

                  fatal_states = (
                  df_shark[df_shark["Fatal Y/N]=="Y"]
                  ["State"]
                  [ ]:
                  # Cleaning the Location column
                  [76]:
                  print(df["Location"].head(20))
                  0                                   Kennedy Shoal
                  +1                           Assateague State Park
                  +2                 Horseshoe Reef  Rottnest Island
                  +3                                         Kooddoo
                  +4     Middleton Beach Fleurieu Peninsula Adelaide
                  +5                                     Fresh Creek
                  +6                      Cape Jaffa Limestone Coast
                  +7                             Little Avalon Beach
                  +8                Big River Beach Mendocino County
                  +9                                         Exmouth
                  +10                                        Exmouth
                  +11                            Lady Elliott Island
                  +12                    Anse Vata near Point Magnin
                  +13                                  Caribbean Sea
                  +14                     Del Chifre Beach in Olinda
                  +15                      Angels Beach East Ballina
                  +16                     Cooee Beach west of Burnie
                  +17          Point Plomber North of Port Macquarie
                  +18                                        Dee Why
                  +19                                   North Steyne
                  +Name: Location, dtype: str
                  +
                  [77]:
                  print(df["Location"].nunique()) # check the total of unique values
                  4588
                  +
                  [78]:
                  print(df["Location"].isna().sum()) #check the sum of na
                  567
                  +
                  [79]:
                  df["Location"] = (
                  df["Location"]
                  .astype(str) # define the type as string.
                  .str.strip() #suppress the non needed spaces.
                  .str.replace(r"\s+", " ", regex=True)
                  .str.replace(r"[.,]+$", " ", regex=True) #suppress punctuation at the end of the text
                  .str.title()
                  )

                  [80]:
                  # We restore the actual missing values.
                  import numpy as np

                  df["Location"] = df["Location"].replace(
                  ["Nan", "None", " ", "Unkown"],
                  np.nan
                  )

                  [81]:
                  # As the dataset conatains distances in miles or kms we will suppress them to keep only the location name.
                  # So that we have: before==> 10 miles north of Durban, after ==> North of Durban.
                  df["Location"] = df["Location"].str.replace(
                  r"\b\d+(\.\d+)?\s*(mile|miles|km|kilometer|kilometers|yard|yards|meter|meters|m)\b",
                  " ",
                  regex=True,
                  case=False
                  )

                  [82]:
                  # We strip out the remaining numbers.
                  df["Location"] = df["Location"].str.replace(
                  r"\b\d+\b",
                  " ",
                  regex=True
                  )
                  [83]:
                  # We clean the remained spaces that have been created by the cleansing before.
                  df["Location"] = (
                  df["Location"]
                  .str.replace(r"\s+", " ", regex=True)
                  .str.strip()
                  )

                  [ ]:

                  [ ]:
                  # Now we drop generic location entries as they offer no exploitable information.(offshore, Ocean, At sea, Off coast...)
                  [87]:
                  generic_locations = [
                  "Offshore",
                  "Off Shore",
                  "Off Coast",
                  "At Sea",
                  "Open Sea",
                  "Ocean",
                  "Near Shore",
                  "Unknown"
                  ]

                  df["Location"] = df["Location"].replace(
                  generic_locations,
                  np.nan
                  )
                  [ ]:
                  # finally we check & control.
                  [88]:
                  # check the missing values.
                  df["Location"].isna().sum()
                  [86]:
                  np.int64(573)
                  [198]:
                  df["Location"]
                  [196]:
                  0                                     Kennedy Shoal
                  +1                             Assateague State Park
                  +2                    Horseshoe Reef Rottnest Island
                  +3                                           Kooddoo
                  +4       Middleton Beach Fleurieu Peninsula Adelaide
                  +                           ...                     
                  +7085                                    Roebuck Bay
                  +7086                                            NaN
                  +7087                                 Ocracoke Inlet
                  +7088                           Panama Bay 8ºn, 79ºw
                  +7089            Below The English Fort, Trincomalee
                  +Name: Location, Length: 7090, dtype: str
                  [89]:
                  # check unique localisation values.
                  df["Location"].nunique()
                  [87]:
                  4549
                  [91]:
                  # list the top 20 of the localisations.
                  df["Location"].value_counts().head(20)
                  [89]:
                  Location
                  +New Smyrna Beach, Volusia County                 198
                  +Daytona Beach, Volusia County                     35
                  +Cocoa Beach, Brevard County                       33
                  +Ponce Inlet, Volusia County                       28
                  +Myrtle Beach, Horry County                        22
                  +Melbourne Beach, Brevard County                   21
                  +Isle Of Palms, Charleston County                  18
                  +Durban                                            18
                  +Piedade                                           14
                  +Boa Viagem, Recife                                14
                  +Jensen Beach, Martin County                       13
                  +Jacksonville Beach, Duval County                  13
                  +Ponce Inlet, New Smyrna Beach, Volusia County     13
                  +Juno Beach, Palm Beach County                     12
                  +Ormond Beach, Volusia County                      11
                  +Palm Beach, Palm Beach County                     11
                  +Mossel Bay                                        11
                  +Ahvaz, On The Karun River                         11
                  +New Smyrna Beach                                  10
                  +Florida Keys, Monroe County                       10
                  +Name: count, dtype: int64
                  [ ]:

                  [ ]:
                  # 4. Time Column cleansing
                  # This column is very challenging, the dataset is very dirty but as we were coached by the best of the best we decided to give it a try.
                  # we need to ==> 1.clean the format, 2.Extract an hour when it exists, 3. Create a business‑oriented category(Morning, Afternoon, Evening, Night)
                  [179]:
                  # First we look at the unique values.
                  df['Time'].sample(20)
                  df['Time'].isna().sum()
                  df['Time'].value_counts().head(20)
                  [177]:
                  Time
                  +Afternoon    263
                  +Morning      141
                  +11:00        140
                  +15:00        128
                  +16:00        126
                  +12:00        122
                  +14:00        117
                  +16:30         88
                  +13:00         87
                  +17:30         83
                  +17:00         81
                  +14:30         76
                  +11:30         75
                  +18:00         75
                  +10:00         74
                  +15:30         73
                  +13:30         70
                  +Night         64
                  +10:30         61
                  +09:00         61
                  +Name: count, dtype: int64
                  [180]:
                  # Now we manage the real missing values.
                  df["Time"] = df["Time"].replace(
                  ["None", "", " "],
                  np.nan
                  )
                  [181]:
                  # We then apply a generic cleanup.

                  import numpy as np

                  df["Time"] = (
                  df["Time"]
                  .astype(str) # convert all the values into string/text to uniformize the dataset.
                  .str.strip() # suppress the unecessary spaces at the begining and the end.
                  .str.replace(r"\s+", " ", regex=True) # suppress the double spaces by just one.
                  )


                  [182]:
                  # We confirm that the type is a string :
                  df["Time"].dtype
                  [180]:
                  <StringDtype(storage='python', na_value=nan)>
                  [183]:
                  # We check the suppression of the spaces.
                  df["Time"].sample(20)
                  [181]:
                  4677          NaN
                  +4985          NaN
                  +632         14:00
                  +6780          NaN
                  +2068        17:30
                  +1444        12:00
                  +2706          NaN
                  +2467        03:00
                  +1161        19:30
                  +3491        09:45
                  +2166    Afternoon
                  +2577        12:00
                  +3978        13:30
                  +2110    Afternoon
                  +5517        14:00
                  +6466          NaN
                  +5956          NaN
                  +445         11:00
                  +1342        20:30
                  +5609        17:30
                  +Name: Time, dtype: str
                  [184]:
                  # Now we can standardize the time formats
                  # 1.We replace the format 17h30 => 17:30
                  # 2.We replace "h" by ":" , 17h30 => 17:30
                  # 3.We replace all the dots. ex: 13.30 ==> 13:30


                  df["Time"] = df["Time"].str.replace(r"(\d{1,2})h(\d{0,2})",
                  lambda m: f"{m.group(1)}:{m.group(2) if m.group(2) else '00'}",
                  regex=True)

                  df["Time"] = df["Time"].str.replace(r"^(\d{2})(\d{2})\b",
                  r"\1:\2",
                  regex=True)

                  df["Time"] = df["Time"].str.replace(r"(\d{1,2})\.(\d{2})",
                  r"\1:\2",
                  regex=True)
                  [185]:
                  # We control that the format are now harmonized :

                  df["Time"].value_counts().head(30)

                  [183]:
                  Time
                  +Afternoon    263
                  +Morning      141
                  +11:00        140
                  +15:00        128
                  +16:00        126
                  +12:00        122
                  +14:00        117
                  +16:30         88
                  +13:00         87
                  +17:30         83
                  +17:00         81
                  +14:30         76
                  +11:30         75
                  +18:00         75
                  +10:00         74
                  +15:30         73
                  +13:30         70
                  +Night         64
                  +10:30         61
                  +09:00         61
                  +Evening       45
                  +08:00         43
                  +12:30         42
                  +09:30         39
                  +18:30         36
                  +07:30         35
                  +08:30         34
                  +?             30
                  +07:00         28
                  +19:00         26
                  +Name: count, dtype: int64
                  [186]:
                  # Now we control if some dirty formats remain:

                  df["Time"].str.contains(r"h", na=False).sum()

                  [184]:
                  np.int64(91)
                  [187]:
                  # We check if the HHMM is reduced.

                  df["Time"].str.contains(r"\d{4}", na=False).sum()

                  [185]:
                  np.int64(3)
                  [188]:
                  # Finally we check that format HH.MM have been removed.

                  df["Time"].str.contains(r"\.", na=False).sum()

                  [186]:
                  np.int64(29)
                  [190]:
                  # we need to harmonize the business values periods by using a custom/mapping dictionary.
                  # This will also allow to complete the missing hours data.

                  time_mapping = {
                  "Morning" : "Morning",
                  "Early Morning" : "Morning",
                  "Noon" : "Afternoon",
                  "Midday" : "Afternoon",
                  "Afternoon" : "Afternoon",
                  "Late afternoon" : "Afternoon",
                  "Evening" : "Evening",
                  "Night" : "Night",
                  "Late Night" : "Night",
                  "Dawn" : "Morning",
                  "Before Dawn" : "Morning",
                  "Sunset" : "Evening",
                  "Unknown" : np.nan
                  }

                  df["Time"] = df["Time"].replace(time_mapping)

                  [191]:
                  # Now we extract the hour as we want to create a business variable (morning, afternoon...)No need to keep the minutes or seconds.

                  df["Hour"] = pd.to_numeric(
                  df["Time"].str.extract(r"(\d{1,2})")[0],
                  errors="coerce"
                  )
                  [192]:
                  # Check the extracted hour.
                  # We try to see how many values have not been extracted
                  df["Hour"].describe()
                  df["Hour"].isna().sum()
                  [190]:
                  np.int64(4211)
                  [193]:
                  # Then we to create a business variable that will be usefull for the dashboard.

                  def time_period(hour):
                  if pd.isna(hour):
                  return np.nan
                  elif 5 <= hour < 12:
                  return "Morning"
                  elif 12 <= hour < 17:
                  return "Afternoon"
                  elif 17 <= hour < 21:
                  return "Evening"
                  else:
                  return "Night"
                  [194]:
                  # Application of the variable created.
                  df["Time_Period"] = df["Hour"].apply(time_period)
                  [195]:
                  print(df.columns) # We print all the columns to check the ones we have created.
                  Index(['Date', 'Year', 'Type', 'Country', 'State', 'Location', 'Activity',
                  +       'Name', 'Sex', 'Age', 'Injury', 'Fatal Y/N', 'Time', 'Species ',
                  +       'Source', 'pdf', 'href formula', 'href', 'Case Number', 'Case Number.1',
                  +       'original order', 'Unnamed: 21', 'Unnamed: 22', 'Hour', 'Time_Period',
                  +       'Time_Clean'],
                  +      dtype='str')
                  +
                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:
                  # If we want to visualize the distribution of activity peaks.

                  df["Hour"].value_counts().sort_index()
                  [ ]:
                  # If we want to visualize through a clear graphic

                  df["Hour"].hist(bins=24)
                  [ ]:

                  [152]:

                  [153]:

                  [154]:



                  [155]:

                  [156]:
                  print(df.columns) # We print all the columns to check the ones we have created.
                  Index(['Date', 'Year', 'Type', 'Country', 'State', 'Location', 'Activity',
                  +       'Name', 'Sex', 'Age', 'Injury', 'Fatal Y/N', 'Time', 'Species ',
                  +       'Source', 'pdf', 'href formula', 'href', 'Case Number', 'Case Number.1',
                  +       'original order', 'Unnamed: 21', 'Unnamed: 22', 'Hour', 'Time_Period',
                  +       'Time_Clean'],
                  +      dtype='str')
                  +
                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [159]:
                  # We check in the datframe what it contains.
                  df[['Time', 'Hour', 'Time_Period']].head(20)
                  [157]:
                  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                  TimeHourTime_Period
                  01100:00rsNaN1100:00rs
                  1?NaN?
                  21000:00rsNaN1000:00rs
                  3?NaN?
                  4?NaN?
                  51730:00rsNaN1730:00rs
                  6?NaN?
                  7?NaN?
                  81715:00rsNaN1715:00rs
                  91015:00rsNaN1015:00rs
                  100800:00rsNaN0800:00rs
                  110815:00rsNaN0815:00rs
                  12?NaN?
                  13?NaN?
                  14?NaN?
                  151100:00rsNaN1100:00rs
                  161815:00rsNaN1815:00rs
                  170830:00rsNaN0830:00rs
                  181145:00rsNaN1145:00rs
                  191820:00rsNaN1820:00rs
                  +
                  [ ]:

                  [166]:
                  df[['Time', 'Time_Clean']].sample(20)
                  [164]:
                  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                  TimeTime_Clean
                  6117NaNNaN
                  284309:30NaN
                  3926NaNNaN
                  244407:30NaN
                  56907:00NaN
                  6882NaNNaN
                  3933NaNNaN
                  4680NaNNaN
                  5612NaNNaN
                  181NaNNaN
                  223215:30NaN
                  7066NaNNaN
                  117117:51NaN
                  4386NaNNaN
                  542913:15NaN
                  3797NaNNaN
                  1411NaNNaN
                  344315:30NaN
                  377418:00NaN
                  1616NaNNaN
                  +
                  [167]:
                  # We check the total of non null values.
                  df['Time_Clean'].isna().sum()
                  [165]:
                  np.int64(7012)
                  [170]:
                  # We check the remained format that have HH:MM
                  df.loc[
                  (df['Time_Clean'].isna()) &
                  (df['Time'].str.contains(':', na=False)),
                  'Time'
                  ].value_counts().head(30)
                  [168]:
                  Time
                  +11:00    140
                  +15:00    128
                  +16:00    126
                  +12:00    122
                  +14:00    117
                  +16:30     88
                  +13:00     87
                  +17:30     83
                  +17:00     81
                  +14:30     76
                  +11:30     75
                  +18:00     75
                  +10:00     74
                  +15:30     73
                  +13:30     70
                  +10:30     61
                  +09:00     61
                  +08:00     43
                  +12:30     42
                  +09:30     39
                  +18:30     36
                  +07:30     35
                  +08:30     34
                  +07:00     28
                  +19:00     26
                  +13:20     20
                  +20:00     19
                  +11:45     19
                  +15:45     18
                  +11:15     18
                  +Name: count, dtype: int64
                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [ ]:

                  [35]:
                  rows, cols = df_shark.shape

                  print(f"Number of observations: {rows}")
                  print(f"Number of variables: {cols}")

                  Number of observations: 7090
                  +Number of variables: 23
                  +
                  [36]:
                  df_shark.info()
                  <class 'pandas.DataFrame'>
                  +RangeIndex: 7090 entries, 0 to 7089
                  +Data columns (total 23 columns):
                  + #   Column          Non-Null Count  Dtype  
                  +---  ------          --------------  -----  
                  + 0   Date            7090 non-null   object 
                  + 1   Year            7088 non-null   float64
                  + 2   Type            7072 non-null   str    
                  + 3   Country         7040 non-null   str    
                  + 4   State           6603 non-null   str    
                  + 5   Location        6523 non-null   str    
                  + 6   Activity        6507 non-null   str    
                  + 7   Name            6872 non-null   str    
                  + 8   Sex             6512 non-null   str    
                  + 9   Age             4096 non-null   object 
                  + 10  Injury          7054 non-null   str    
                  + 11  Fatal Y/N       6529 non-null   object 
                  + 12  Time            3563 non-null   object 
                  + 13  Species         3959 non-null   str    
                  + 14  Source          7070 non-null   object 
                  + 15  pdf             6799 non-null   object 
                  + 16  href formula    6794 non-null   str    
                  + 17  href            6796 non-null   str    
                  + 18  Case Number     6798 non-null   str    
                  + 19  Case Number.1   6797 non-null   str    
                  + 20  original order  6799 non-null   float64
                  + 21  Unnamed: 21     1 non-null      str    
                  + 22  Unnamed: 22     2 non-null      str    
                  +dtypes: float64(2), object(6), str(15)
                  +memory usage: 692.5+ KB
                  +
                  [37]:
                  df_shark.isnull().head()
                  [35]:
                  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                  DateYearTypeCountryStateLocationActivityNameSexAge...SpeciesSourcepdfhref formulahrefCase NumberCase Number.1original orderUnnamed: 21Unnamed: 22
                  0FalseFalseFalseFalseFalseFalseFalseFalseFalseFalse...FalseFalseTrueTrueTrueTrueTrueTrueTrueTrue
                  1FalseFalseFalseFalseFalseFalseFalseFalseFalseFalse...FalseFalseTrueTrueTrueTrueTrueTrueTrueTrue
                  2FalseFalseFalseFalseFalseFalseFalseFalseFalseFalse...FalseFalseTrueTrueTrueTrueTrueTrueTrueTrue
                  3FalseFalseFalseFalseFalseFalseFalseFalseFalseFalse...FalseFalseTrueTrueTrueTrueTrueTrueTrueTrue
                  4FalseFalseFalseFalseFalseFalseFalseFalseFalseFalse...FalseFalseTrueTrueTrueTrueTrueTrueTrueTrue
                  +

                  5 rows × 23 columns

                  +
                  [ ]:

                  [ ]:
                  # Col to keep: year _ type _ country _ state _ location _ activity _ age _ sex _ injury _ fatal.
                  [38]:
                  df_shark.isnull().sum()
                  [36]:
                  Date                 0
                  +Year                 2
                  +Type                18
                  +Country             50
                  +State              487
                  +Location           567
                  +Activity           583
                  +Name               218
                  +Sex                578
                  +Age               2994
                  +Injury              36
                  +Fatal Y/N          561
                  +Time              3527
                  +Species           3131
                  +Source              20
                  +pdf                291
                  +href formula       296
                  +href               294
                  +Case Number        292
                  +Case Number.1      293
                  +original order     291
                  +Unnamed: 21       7089
                  +Unnamed: 22       7088
                  +dtype: int64
                  [42]:
                  df_shark_geo = df_shark[["State", "Location", "Time"]] # We want to select the columns State, Location & Time
                  df_shark_geo.head()
                  [40]:
                  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
                  StateLocationTime
                  0QueenslandKennedy Shoal1100hrs
                  1MarylandAssateague State Park?
                  2Western AustraliaHorseshoe Reef  Rottnest Island1000hrs
                  3Gaafu Alif AtollKooddoo?
                  4South AustraliaMiddleton Beach Fleurieu Peninsula Adelaide?
                  +
                  [43]:
                  type(df_shark_geo) # to understand the type of df_shark = it is panda dataframe
                  [41]:
                  pandas.DataFrame
                  [44]:
                  df_shark_geo.isnull().sum() # we combine isnull() with sum() to have the total of nulls per column.
                  [42]:
                  State        487
                  +Location     567
                  +Time        3527
                  +dtype: int64
                  [45]:
                  # Find the most common value (mode) of State
                  most_common_state = df_shark_geo["State"].mode()[0]
                  print("Most common state:", most_common_state)
                  Most common state: Florida
                  +
                  [50]:
                  # Fill the nulls in State with the most common state
                  # First step of our cleaning workflow, we save it as df_geo_clean
                  df_geo_clean = df_shark_geo.fillna({"State": most_common_state})

                  # then we verify that there is no more null in State
                  print("Nulls in State after fillna:", df_geo_clean["State"].isnull().sum())
                  print("Total rows :", df_geo_clean.shape[0])
                  Nulls in State after fillna: 0
                  +Total rows : 7090
                  +
                  [51]:
                  # Find the most common value (mode) of Location
                  most_common_location = df_shark_geo["Location"].mode()[0]
                  print("Most common location:", most_common_location)
                  Most common location: New Smyrna Beach, Volusia County
                  +
                  [52]:
                  # We do the same exercise with the column Location now:
                  df_loca_clean = df_shark_geo.fillna({"Location": most_common_location})

                  print("Nulls in Location after fillna:", df_loca_clean["Location"].isnull().sum())
                  print("Total rows :", df_loca_clean.shape[0])
                  Nulls in Location after fillna: 0
                  +Total rows : 7090
                  +
                  [53]:
                  # Find the most common value (mode) of Time
                  most_common_time = df_shark_geo["Time"].mode()[0]
                  print("Most common time:", most_common_time)
                  Most common time: Afternoon
                  +
                  [55]:
                  # We do the same exercise with the column Time now:
                  df_time_clean = df_shark_geo.fillna({"Time": most_common_time})

                  print("Nulls in Time after fillna:", df_time_clean["Time"].isnull().sum())
                  print("Total rows :", df_time_clean.shape[0])
                  Nulls in Time after fillna: 0
                  +Total rows : 7090
                  +
                  [ ]:
                  #Clean the
                  df_shark_geo["State"] = df_shark_geo["State"].str.strip()
                  [57]:
                  # Check the current data types of the shark dataset
                  df_shark.dtypes
                  [55]:
                  Date               object
                  +Year              float64
                  +Type                  str
                  +Country               str
                  +State                 str
                  +Location              str
                  +Activity              str
                  +Name                  str
                  +Sex                   str
                  +Age                object
                  +Injury                str
                  +Fatal Y/N          object
                  +Time               object
                  +Species               str
                  +Source             object
                  +pdf                object
                  +href formula          str
                  +href                  str
                  +Case Number           str
                  +Case Number.1         str
                  +original order    float64
                  +Unnamed: 21           str
                  +Unnamed: 22           str
                  +dtype: object
                  [ ]:
                  # "Time" is stored as "object"
                  # Pclass: 1, 2, or 3 --> First, Second, Third class
                  # Survived: 0 = No, 1 = Yes --> not real numbers

                  # Convert them from int64 to object (treat them as categories, not numbers)
                  # This returns a NEW DataFrame; we assign it back to keep the change
                  titanic_clean_df[["Pclass","Survived"]] = titanic_clean_df[["Pclass","Survived"]].astype("object")

                  # Verify the new dtypes (plural, because we are looking at multiple columns)
                  print("Dtypes of Pclass and Survived after astype:")
                  print(titanic_clean_df[["Pclass","Survived"]].dtypes)
                    [ ]:
                    #API

                    [3]:
                    !pip install requests
                    mambajs 0.21.1
                    +
                    +Process pip requirements ...
                    +
                    +Successfully installed requests-2.34.2 charset_normalizer-3.4.7 idna-3.18 urllib3-2.7.0 certifi-2026.5.20
                    +
                    [4]:
                    #Install the library (only needed once)
                    # !pip install requests.
                    import requests
                    [ ]:
                    #endpoint : satellites
                    [8]:
                    # Step 1 : chose and save the URL
                    iss_url_sat = "https://api.wheretheiss.at/v1/satellites"

                    # Step 2a: Send the request and save it as a response.
                    # Step 2b: Check the status
                    iss_response_sat = requests.get(iss_url_sat)
                    iss_response_sat.status_code
                    [7]:
                    200
                    [9]:
                    # Step3 - parse the response (to a python dict)

                    iss_data_sat = iss_response_sat.json()
                    iss_data_sat
                    [8]:
                    [{'name': 'iss', 'id': 25544}]
                    [10]:
                    # Respond headers : all metadata sent by the server
                    iss_response_sat.headers
                    [9]:
                    {'cache-control': 'max-age=0, no-cache', 'content-length': '27', 'content-type': 'application/json'}
                    [ ]:
                    satelitte_id = iss_data_sat[0]["id"]
                    [ ]:

                    Notebook
                    Python 3.13 (XPython)
                    Kernel status: Initializing
                      # APIS

                      1. APIS¶

                      +
                      ![apis_gif](https://media.giphy.com/media/wepUQluC5smgEd4Qz4/giphy.gif)

                      apis_gif

                      +
                      <h1>Table of Contents<span class="tocSkip"></span></h1>
                      <div class="toc"><ul class="toc-item"><li><span><a href="#APIS" data-toc-modified-id="APIS-1"><span class="toc-item-num">1&nbsp;&nbsp;</span>APIS</a></span><ul class="toc-item"><li><span><a href="#Web-Communication-Fundamentals" data-toc-modified-id="Web-Communication-Fundamentals-1.1"><span class="toc-item-num">1.1&nbsp;&nbsp;</span>Web Communication Fundamentals</a></span><ul class="toc-item"><li><span><a href="#DNS,-IP" data-toc-modified-id="DNS,-IP-1.1.1"><span class="toc-item-num">1.1.1&nbsp;&nbsp;</span>DNS, IP</a></span></li><li><span><a href="#HTTP" data-toc-modified-id="HTTP-1.1.2"><span class="toc-item-num">1.1.2&nbsp;&nbsp;</span>HTTP</a></span></li><li><span><a href="#URL" data-toc-modified-id="URL-1.1.3"><span class="toc-item-num">1.1.3&nbsp;&nbsp;</span>URL</a></span></li></ul></li><li><span><a href="#HTTP-Requests-and-Responses" data-toc-modified-id="HTTP-Requests-and-Responses-1.2"><span class="toc-item-num">1.2&nbsp;&nbsp;</span>HTTP Requests and Responses</a></span><ul class="toc-item"><li><span><a href="#Requests" data-toc-modified-id="Requests-1.2.1"><span class="toc-item-num">1.2.1&nbsp;&nbsp;</span>Requests</a></span></li><li><span><a href="#Response" data-toc-modified-id="Response-1.2.2"><span class="toc-item-num">1.2.2&nbsp;&nbsp;</span>Response</a></span></li></ul></li><li><span><a href="#APIs" data-toc-modified-id="APIs-1.3"><span class="toc-item-num">1.3&nbsp;&nbsp;</span>APIs</a></span><ul class="toc-item"><li><span><a href="#RESTful-APIs" data-toc-modified-id="RESTful-APIs-1.3.1"><span class="toc-item-num">1.3.1&nbsp;&nbsp;</span>RESTful APIs</a></span></li></ul></li><li><span><a href="#Requests-in-Python" data-toc-modified-id="Requests-in-Python-1.4"><span class="toc-item-num">1.4&nbsp;&nbsp;</span>Requests in Python</a></span><ul class="toc-item"><li><span><a href="#GET" data-toc-modified-id="GET-1.4.1"><span class="toc-item-num">1.4.1&nbsp;&nbsp;</span>GET</a></span><ul class="toc-item"><li><span><a href="#Accessing-an-endpoint" data-toc-modified-id="Accessing-an-endpoint-1.4.1.1"><span class="toc-item-num">1.4.1.1&nbsp;&nbsp;</span>Accessing an endpoint</a></span></li><li><span><a href="#Response-headers" data-toc-modified-id="Response-headers-1.4.1.2"><span class="toc-item-num">1.4.1.2&nbsp;&nbsp;</span>Response headers</a></span></li><li><span><a href="#Parameters" data-toc-modified-id="Parameters-1.4.1.3"><span class="toc-item-num">1.4.1.3&nbsp;&nbsp;</span>Parameters</a></span></li><li><span><a href="#Request-Headers" data-toc-modified-id="Request-Headers-1.4.1.4"><span class="toc-item-num">1.4.1.4&nbsp;&nbsp;</span>Request Headers</a></span></li></ul></li></ul></li><li><span><a href="#APIs-examples" data-toc-modified-id="APIs-examples-1.5"><span class="toc-item-num">1.5&nbsp;&nbsp;</span>APIs examples</a></span><ul class="toc-item"><li><span><a href="#NewsAPI" data-toc-modified-id="NewsAPI-1.5.1"><span class="toc-item-num">1.5.1&nbsp;&nbsp;</span>NewsAPI</a></span><ul class="toc-item"><li><span><a href="#💡-Check-for-understanding" data-toc-modified-id="💡-Check-for-understanding-1.5.1.1"><span class="toc-item-num">1.5.1.1&nbsp;&nbsp;</span>💡 Check for understanding</a></span></li></ul></li><li><span><a href="#Pokemon-API" data-toc-modified-id="Pokemon-API-1.5.2"><span class="toc-item-num">1.5.2&nbsp;&nbsp;</span>Pokemon API</a></span><ul class="toc-item"><li><span><a href="#json_normalize()" data-toc-modified-id="json_normalize()-1.5.2.1"><span class="toc-item-num">1.5.2.1&nbsp;&nbsp;</span>json_normalize()</a></span></li></ul></li><li><span><a href="#Coincap-API" data-toc-modified-id="Coincap-API-1.5.3"><span class="toc-item-num">1.5.3&nbsp;&nbsp;</span>Coincap API</a></span></li><li><span><a href="#Api-Jokes" data-toc-modified-id="Api-Jokes-1.5.4"><span class="toc-item-num">1.5.4&nbsp;&nbsp;</span>Api Jokes</a></span></li><li><span><a href="#💡-Check-for-understanding" data-toc-modified-id="💡-Check-for-understanding-1.5.5"><span class="toc-item-num">1.5.5&nbsp;&nbsp;</span>💡 Check for understanding</a></span></li></ul></li><li><span><a href="#API-Wrappers" data-toc-modified-id="API-Wrappers-1.6"><span class="toc-item-num">1.6&nbsp;&nbsp;</span>API Wrappers</a></span></li><li><span><a href="#Summary" data-toc-modified-id="Summary-1.7"><span class="toc-item-num">1.7&nbsp;&nbsp;</span>Summary</a></span></li><li><span><a href="#Glossary" data-toc-modified-id="Glossary-1.8"><span class="toc-item-num">1.8&nbsp;&nbsp;</span>Glossary</a></span></li><li><span><a href="#Further-materials" data-toc-modified-id="Further-materials-1.9"><span class="toc-item-num">1.9&nbsp;&nbsp;</span>Further materials</a></span></li></ul></li></ul></div>

                      Table of Contents¶

                      +
                      • 1  APIS
                        • 1.1  Web Communication Fundamentals
                          • 1.1.1  DNS, IP
                          • 1.1.2  HTTP
                          • 1.1.3  URL
                        • 1.2  HTTP Requests and Responses
                          • 1.2.1  Requests
                          • 1.2.2  Response
                        • 1.3  APIs
                          • 1.3.1  RESTful APIs
                        • 1.4  Requests in Python
                          • 1.4.1  GET
                            • 1.4.1.1  Accessing an endpoint
                            • 1.4.1.2  Response headers
                            • 1.4.1.3  Parameters
                            • 1.4.1.4  Request Headers
                        • 1.5  APIs examples
                          • 1.5.1  NewsAPI
                            • 1.5.1.1  💡 Check for understanding
                          • 1.5.2  Pokemon API
                            • 1.5.2.1  json_normalize()
                          • 1.5.3  Coincap API
                          • 1.5.4  Api Jokes
                          • 1.5.5  💡 Check for understanding
                        • 1.6  API Wrappers
                        • 1.7  Summary
                        • 1.8  Glossary
                        • 1.9  Further materials
                      ## Web Communication Fundamentals

                      1.1. Web Communication Fundamentals¶

                      +
                      ![enjuto](https://www.publico.es/files/article_main/uploads//2014/12/13/548bb18f1d9ef.jpg)

                      enjuto

                      +
                      ### DNS, IP

                      1.1.1. DNS, IP¶

                      +
                      [How do we connect to www.google.com?](https://www.youtube.com/watch?v=sUhEqT_HSBI&ab_channel=ProfeSang)
                      * **DNS (Domain Name System)**: it's essentially the phonebook of the internet. Humans access information online through domain names, like "google.com". Web browsers, however, interact through Internet Protocol (IP) addresses. In this example, the DNS maps the internet address www.google.com to the server's IP: 216.58.222.196
                      * **IP**: Server identification. A code that allows information to be sent and received by the correct parties
                      * **Domain Providers**: Sell and purchase Internet domains

                      How do we connect to www.google.com?

                      +
                        +
                      • DNS (Domain Name System): it's essentially the phonebook of the internet. Humans access information online through domain names, like "google.com". Web browsers, however, interact through Internet Protocol (IP) addresses. In this example, the DNS maps the internet address www.google.com to the server's IP: 216.58.222.196
                      • +
                      • IP: Server identification. A code that allows information to be sent and received by the correct parties
                      • +
                      • Domain Providers: Sell and purchase Internet domains
                      • +
                      +
                      *When making API calls to specific domains, the DNS translates the human-readable domain name into an IP address.*

                      When making API calls to specific domains, the DNS translates the human-readable domain name into an IP address.

                      +
                      ### HTTP

                      1.1.2. HTTP¶

                      +
                      **H**yper **T**ext **T**ransfer **P**rotocol
                      - HTTP is a communications protocol that provides a structure for requests between the client and the server on a network.
                      - For example, the web browser on the user's computer (the client) uses the HTTP protocol to request information from a website on a server.

                      *We will use APIs that employ the **HTTP** protocol as transport.*

                      Hyper Text Transfer Protocol

                      +
                        +
                      • HTTP is a communications protocol that provides a structure for requests between the client and the server on a network.
                      • +
                      • For example, the web browser on the user's computer (the client) uses the HTTP protocol to request information from a website on a server.
                      • +
                      +

                      We will use APIs that employ the HTTP protocol as transport.

                      +
                      ### URL
                      Contains information about the resource being requested from the **server**.

                      1.1.3. URL¶

                      +

                      Contains information about the resource being requested from the server.

                      +
                      ![](https://github.com/data-bootcamp-v4/lessons/blob/main/img/http.png?raw=true)

                      Image

                      +
                      Examples:

                      - https://www.google.com/webhp?authuser=2
                      - https://www.towardsdatascience.com
                      - https://www.ironhack.com/
                      - Protocol: https (https == http is the same, but https is encrypted)
                      - Domain Name --> ironhack
                      - TLD (Top-Level Domain) --> .com

                      Examples:

                      +
                        +
                      • https://www.google.com/webhp?authuser=2
                      • +
                      • https://www.towardsdatascience.com
                      • +
                      • https://www.ironhack.com/
                          +
                        • Protocol: https (https == http is the same, but https is encrypted)
                        • +
                        • Domain Name --> ironhack
                        • +
                        • TLD (Top-Level Domain) --> .com
                        • +
                        +
                      • +
                      +
                      *When calling an API, you specify the URL of the API endpoint, which tells the system where to send the request.*

                      When calling an API, you specify the URL of the API endpoint, which tells the system where to send the request.

                      +
                      ## HTTP Requests and Responses

                      1.2. HTTP Requests and Responses¶

                      +
                      ![](https://github.com/data-bootcamp-v4/lessons/blob/main/img/request-response.png?raw=true)

                      Image

                      +
                      **Requests** and **responses** are fundamental components of the client-server communication model.

                      Requests and responses are fundamental components of the client-server communication model.

                      +
                      ### Requests

                      1.2.1. Requests¶

                      +
                      These are queries or calls **sent by the client** (such as a web browser or other software) **to the server** in order to receive information (a **response**).

                      A request typically consists of:
                      - A method (such as GET, POST, PUT, DELETE) that defines the action to be performed
                      * GET: read the information of the resource, without modifying it in any way. Accessing the website from the browser **gets** information.
                      - The URL or endpoint specifying the resource
                      - Optional additional information such as:
                      - Headers (metadata): User-Agent, Accept-Language
                      - Parameters
                      - Body content.
                      For example, a client might send a GET request to retrieve information from a web page or a POST request to submit form data.

                      These are queries or calls sent by the client (such as a web browser or other software) to the server in order to receive information (a response).

                      +

                      A request typically consists of:

                      +
                        +
                      • A method (such as GET, POST, PUT, DELETE) that defines the action to be performed
                          +
                        • GET: read the information of the resource, without modifying it in any way. Accessing the website from the browser gets information.
                        • +
                        +
                      • +
                      • The URL or endpoint specifying the resource
                      • +
                      • Optional additional information such as:
                          +
                        • Headers (metadata): User-Agent, Accept-Language
                        • +
                        • Parameters
                        • +
                        • Body content.
                        • +
                        +
                      • +
                      +

                      For example, a client might send a GET request to retrieve information from a web page or a POST request to submit form data.

                      +
                      ### Response

                      1.2.2. Response¶

                      +
                      These are the answers or data sent **by the server back to the client in reply to a request**.

                      A response typically includes:
                      - A status code that indicates the success or failure of the request
                      - Headers with meta-information about the server's behavior
                      - The actual content or data (if applicable), such as HTML, JSON (similar to a Python dictionary), images, or other media types.

                      An important part of the **header** is the **status code**. This code is a numerical value that indicates the server's result. There are different status codes depending on whether the server has managed to carry out the request or has not managed to do anything. These are some groups of status codes:

                      - **2xx successful**: the request was successfully received, understood, and accepted
                      - **3xx redirection**: more actions are required to complete the request

                      These are the answers or data sent by the server back to the client in reply to a request.

                      +

                      A response typically includes:

                      +
                        +
                      • A status code that indicates the success or failure of the request
                      • +
                      • Headers with meta-information about the server's behavior
                      • +
                      • The actual content or data (if applicable), such as HTML, JSON (similar to a Python dictionary), images, or other media types.
                      • +
                      +

                      An important part of the header is the status code. This code is a numerical value that indicates the server's result. There are different status codes depending on whether the server has managed to carry out the request or has not managed to do anything. These are some groups of status codes:

                      +
                        +
                      • 2xx successful: the request was successfully received, understood, and accepted
                      • +
                      • 3xx redirection: more actions are required to complete the request
                      • +
                      • 4xx client error: the request contains incorrect syntax or cannot be fulfilled
                      • +
                      • 5xx server error: the server has failed to complete an apparently valid request
                      • +
                      +

                      Complete list: +https://en.wikipedia.org/wiki/List_of_HTTP_status_codes

                      +

                      Much more fun: +https://http.cat/

                      +
                      ## APIs

                      1.3. APIs¶

                      +
                      **A**pplication **P**rogramming **I**nterface

                      - APIs define a set of rules and protocols that allow different software applications to communicate with each other.
                      - The client calls the server through an API, and the server responds.
                      - Within a project, the backend may want to share information with the frontend without access to the database.

                      Application Programming Interface

                      +
                        +
                      • APIs define a set of rules and protocols that allow different software applications to communicate with each other.
                      • +
                      • The client calls the server through an API, and the server responds.
                      • +
                      • Within a project, the backend may want to share information with the frontend without access to the database.
                      • +
                      +
                      ![](https://github.com/data-bootcamp-v4/lessons/blob/main/img/api.png?raw=true)

                      Image

                      +
                      ### RESTful APIs

                      1.3.1. RESTful APIs¶

                      +
                      As a data analyst, you often need to access data from various sources. RESTful APIs provide a **standardized way** to retrieve, update, or delete data from other systems.

                      As a data analyst, you often need to access data from various sources. RESTful APIs provide a standardized way to retrieve, update, or delete data from other systems.

                      +
                      - Usually, **we (the client) send a request, and they (the server) return a response, often in JSON - JavaScript Object Notation - format**. While JSON looks similar to the dictionaries we're used to in Python, it's worth noting that when represented as raw text (e.g., in an API response), the entire JSON structure is a *string*. However, within that structure, JSON can represent various data types like numbers, booleans, and arrays, not just strings.

                      - APIs always need to provide documentation for their various services: **endpoints**. Each endpoint is a different URL.
                      - Sometimes we have to pass **parameters** to an API endpoint, similar to when we pass parameters to a Python function.

                      At this point in the Bootcamp, **reading documentation becomes essential**, as each API will "work" differently, and to use it, we will need to know what it requires.

                      [Here](https://github.com/public-apis/public-apis) is a repository where you can find several free APIs if you feel like exploring a bit.
                        +
                      • Usually, we (the client) send a request, and they (the server) return a response, often in JSON - JavaScript Object Notation - format. While JSON looks similar to the dictionaries we're used to in Python, it's worth noting that when represented as raw text (e.g., in an API response), the entire JSON structure is a string. However, within that structure, JSON can represent various data types like numbers, booleans, and arrays, not just strings.

                        +
                      • +
                      • APIs always need to provide documentation for their various services: endpoints. Each endpoint is a different URL.

                        +
                      • +
                      • Sometimes we have to pass parameters to an API endpoint, similar to when we pass parameters to a Python function.

                        +
                      • +
                      +

                      At this point in the Bootcamp, reading documentation becomes essential, as each API will "work" differently, and to use it, we will need to know what it requires.

                      +

                      Here is a repository where you can find several free APIs if you feel like exploring a bit.

                      +
                      ## Requests in Python

                      1.4. Requests in Python¶

                      +
                      You can make requests to RESTful APIs using libraries like `requests`.

                      You can make requests to RESTful APIs using libraries like requests.

                      +
                      [ ]:
                      # If you don't have it installed you can do use using pip or pip3

                      #!pip install requests
                      [ ]:
                      import requests
                      ### GET

                      1.4.1. GET¶

                      +
                      Making a `GET request` to the API is simply a call to a `URL` that returns information when provided with the appropriate `parameters`. We will only perform **GET** requests.

                      Making a GET request to the API is simply a call to a URL that returns information when provided with the appropriate parameters. We will only perform GET requests.

                      +
                      Here is an example on how to make a request with Python:

                      ```python
                      url = "https://api.example.com/products"
                      response = requests.get(url)

                      if response.status_code == 200:
                      products = response.json()
                      # Now you can analyze and work with the products data in Python
                      ```

                      Here is an example on how to make a request with Python:

                      +
                      url = "https://api.example.com/products"
                      +response = requests.get(url)
                      +
                      +if response.status_code == 200:
                      +    products = response.json()
                      +    # Now you can analyze and work with the products data in Python
                      #### Accessing an endpoint

                      As we mentioned above, APIs always need to provide documentation for their various services: **endpoints**. Each endpoint is a different URL.

                      **Example: ISS API**

                      1.4.1.1. Accessing an endpoint¶

                      +

                      As we mentioned above, APIs always need to provide documentation for their various services: endpoints. Each endpoint is a different URL.

                      +

                      Example: ISS API

                      +
                      Let's get information from ISS (International Space Station)! We'll start looking at the [ISS API documentation](https://wheretheiss.at/w/developer)

                      Let's get information from ISS (International Space Station)! We'll start looking at the ISS API documentation

                      +
                      This API allows you to access various data related to the International Space Station (ISS), including its current, past, or future position, timezone information for specific coordinates, and more.

                      **Key Features**
                      - **Authentication**: No authentication is currently required, but future endpoints may include this.
                      - **Rate Limiting**: Limited to approximately 1 request per second.
                      - **Responses**: Default to JSON format, with optional parameters to modify response appearance.
                      - **Endpoints**: Several endpoints provide different types of information:
                      - **satellites**: Information about satellites, including the ISS.
                      - **satellites/[id]**: Position, velocity, and related information for a satellite.
                      - **satellites/[id]/positions**: Position data for specific timestamps.
                      - **satellites/[id]/tles**: TLE (Two-Line Element Set) data in either JSON or text format.

                      This API allows you to access various data related to the International Space Station (ISS), including its current, past, or future position, timezone information for specific coordinates, and more.

                      +

                      Key Features

                      +
                        +
                      • Authentication: No authentication is currently required, but future endpoints may include this.
                      • +
                      • Rate Limiting: Limited to approximately 1 request per second.
                      • +
                      • Responses: Default to JSON format, with optional parameters to modify response appearance.
                      • +
                      • Endpoints: Several endpoints provide different types of information:
                          +
                        • satellites: Information about satellites, including the ISS.
                        • +
                        • satellites/[id]: Position, velocity, and related information for a satellite.
                        • +
                        • satellites/[id]/positions: Position data for specific timestamps.
                        • +
                        • satellites/[id]/tles: TLE (Two-Line Element Set) data in either JSON or text format.
                        • +
                        • coordinates/[lat,lon]: Timezone information for specific coordinates.
                        • +
                        +
                      • +
                      +
                      **Examples**
                      - Satellite details: `https://api.wheretheiss.at/v1/satellites`
                      - ISS position: `https://api.wheretheiss.at/v1/satellites/25544`
                      - Coordinates information: `https://api.wheretheiss.at/v1/coordinates/37.795517,-122.393693`


                      Examples

                      +
                        +
                      • Satellite details: https://api.wheretheiss.at/v1/satellites
                      • +
                      • ISS position: https://api.wheretheiss.at/v1/satellites/25544
                      • +
                      • Coordinates information: https://api.wheretheiss.at/v1/coordinates/37.795517,-122.393693
                      • +
                      +
                      **Endpoint satellites**

                      Endpoint satellites

                      +
                      [ ]:
                      # We'll use the endpoint satellites, we read it gives us information about satellites

                      url = "https://api.wheretheiss.at/v1/satellites"
                      [ ]:
                      response = requests.get(url) # We use get method to make the request and get information from the API
                      [ ]:
                      type(response) # Lets look at the type of the response
                      [ ]:
                      response.status_code # We can access status code. 200 is OK
                      We can access the response content, which returns a string

                      We can access the response content, which returns a string

                      +
                      [ ]:
                      response.content
                      If response Content-Type is json, we can access it better with `.json()`

                      If response Content-Type is json, we can access it better with .json()

                      +
                      [ ]:
                      info = response.json()
                      [ ]:
                      info
                      [ ]:
                      type(info) # It returns a list of dictionaries
                      [ ]:
                      info[0]["id"] # So we can access the id like this
                      #### Response headers

                      Response headers are part of the HTTP response that a server sends back to the client after a request has been made. These headers provide meta-information about the response and can affect how the client handles the response.

                      Here are some common response headers and what they typically represent:

                      1. **`Date`**: Represents the date and time at which the response was sent.

                      2. **`Server`**: Provides information about the software used by the originating server.

                      3. **`X-Rate-Limit`**: Sometimes used in APIs to inform the client about rate limiting policies, such as the number of allowed requests in a given time frame.

                      4. **`Content-Type`**: Specifies the media type of the resource or data the server is sending back. For example, it could be `application/json` for a JSON object, `text/html` for an HTML page, or `image/png` for an image.

                      1.4.1.2. Response headers¶

                      +

                      Response headers are part of the HTTP response that a server sends back to the client after a request has been made. These headers provide meta-information about the response and can affect how the client handles the response.

                      +

                      Here are some common response headers and what they typically represent:

                      +
                        +
                      1. Date: Represents the date and time at which the response was sent.

                        +
                      2. +
                      3. Server: Provides information about the software used by the originating server.

                        +
                      4. +
                      5. X-Rate-Limit: Sometimes used in APIs to inform the client about rate limiting policies, such as the number of allowed requests in a given time frame.

                        +
                      6. +
                      7. Content-Type: Specifies the media type of the resource or data the server is sending back. For example, it could be application/json for a JSON object, text/html for an HTML page, or image/png for an image.

                        +
                      8. +
                      +

                      And more.

                      +
                      [ ]:
                      response.headers # We can also access the response headers
                      #### Parameters

                      1.4.1.3. Parameters¶

                      +
                      As we mentioned above, sometimes we can pass **parameters** to an API endpoint, similar to when we pass parameters to a Python function.

                      As we mentioned above, sometimes we can pass parameters to an API endpoint, similar to when we pass parameters to a Python function.

                      +
                      In the example above, we didn't use any parameters in the endpoint `satellites` since the [documentation]("https://wheretheiss.at/w/developer") said *Parameters: None*.

                      In the example above, we didn't use any parameters in the endpoint satellites since the documentation said Parameters: None.

                      +
                      API parameters are specific values that you include in a request to an API endpoint to filter, sort, or detail the data that you want to retrieve. They allow you to customize the request to get exactly the information you need.

                      API parameters are specific values that you include in a request to an API endpoint to filter, sort, or detail the data that you want to retrieve. They allow you to customize the request to get exactly the information you need.

                      +
                      There are several types of parameters that can be used in API requests, such as Path Parameters, Query Parameters, Header Parameters and Request Body Parameters. Let's look at them with an example using another endpoint.

                      There are several types of parameters that can be used in API requests, such as Path Parameters, Query Parameters, Header Parameters and Request Body Parameters. Let's look at them with an example using another endpoint.

                      +
                      ##### 1. **Path Parameters**

                      These are embedded in the URL path and are used to identify a specific resource. For example, in the URL above `https://api.wheretheiss.at/v1/satellites/25544`, the number `25544` is a path parameter that identifies a specific sattelite.
                      1. Path Parameters¶
                      +

                      These are embedded in the URL path and are used to identify a specific resource. For example, in the URL above https://api.wheretheiss.at/v1/satellites/25544, the number 25544 is a path parameter that identifies a specific sattelite.

                      +
                      **Endpoint satellites/id**

                      Endpoint satellites/id

                      +
                      Lets use endpoint satellites/[id] with the id we got from the previous endpoint.
                      Important, we need to provide the whole url for the endpoint as a `string`.

                      Lets use endpoint satellites/[id] with the id we got from the previous endpoint. +Important, we need to provide the whole url for the endpoint as a string.

                      +
                      [ ]:
                      url_iss_position = "https://api.wheretheiss.at/v1/satellites/"+str(info[0]["id"])
                      [ ]:
                      response = requests.get(url_iss_position)
                      [ ]:
                      response.status_code
                      [ ]:
                      response.json()
                      Every time we call the endpoint, the information changes since it gives us the current position of the satellite. Let's check that with a loop to gather information at different times.

                      Every time we call the endpoint, the information changes since it gives us the current position of the satellite. Let's check that with a loop to gather information at different times.

                      +
                      [ ]:
                      import time

                      positions = []

                      for i in range(10):
                      response = requests.get(url_iss_position)
                      data = response.json()
                      positions.append(data)
                      time.sleep(0.5)
                      [ ]:
                      type(positions) # We created a list with responses
                      [ ]:
                      type(positions[0]) # Is a list of dictionaries
                      [ ]:
                      positions[0] # So we can access each dictionary by its position in the list
                      [ ]:
                      positions[9]
                      Let's extract 10 latitudes from the 10 dictionaries in the list, with 2 decimals.

                      Let's extract 10 latitudes from the 10 dictionaries in the list, with 2 decimals.

                      +
                      [ ]:
                      latitudes = [round(p["latitude"], 2) for p in positions]
                      [ ]:
                      latitudes
                      [ ]:
                      import pandas as pd
                      pd.DataFrame(positions) # We can create a dataframe from the API response to work with it better

                      ##### 2. **Query Parameters**

                      These are added to the end of the URL after a question mark (`?`) and are often used to filter or sort the response.

                      For example, in the ISS documentation, it mentions that there is a parameter `units` that can take values `miles` or `kilometers`. So in the URL `https://api.wheretheiss.at/v1/satellites/25544?units=miles`, we added `units=miles` which is a query parameter that shows the data in "miles".

                      In general, we add the parameters like this `?param1=value1&param2=value2...` at the end of the URL.
                      2. Query Parameters¶
                      +

                      These are added to the end of the URL after a question mark (?) and are often used to filter or sort the response.

                      +

                      For example, in the ISS documentation, it mentions that there is a parameter units that can take values miles or kilometers. So in the URL https://api.wheretheiss.at/v1/satellites/25544?units=miles, we added units=miles which is a query parameter that shows the data in "miles".

                      +

                      In general, we add the parameters like this ?param1=value1&param2=value2... at the end of the URL.

                      +
                      [ ]:
                      url_iss_position
                      [ ]:
                      # We saw in the documentation that we can add a parameter units to use miles or kilometers

                      url_iss_position2 = 'https://api.wheretheiss.at/v1/satellites/25544?units=miles'
                      [ ]:
                      response = requests.get(url_iss_position2)
                      [ ]:
                      response.status_code
                      [ ]:
                      response.json()
                      We can also do it by passing to the argument `params` a dictionary with the parameters in the `get` method.

                      We can also do it by passing to the argument params a dictionary with the parameters in the get method.

                      +
                      [ ]:
                      parameters = {"units": "miles"}
                      [ ]:
                      url_iss_position
                      [ ]:
                      response = requests.get(
                      url=url_iss_position,
                      params=parameters
                      )
                      [ ]:
                      response.json()

                      ##### 3. **Header Parameters**

                      These are included in the request header and can be used for various purposes, such as authentication (e.g., sending an API key or token), content negotiation (e.g., defining the response format), or custom settings defined by the API. If the API requires authentication, you might include an `Authorization` header with your API key.
                      3. Header Parameters¶
                      +

                      These are included in the request header and can be used for various purposes, such as authentication (e.g., sending an API key or token), content negotiation (e.g., defining the response format), or custom settings defined by the API. If the API requires authentication, you might include an Authorization header with your API key.

                      +
                      ISS API mentions that *currently there is no authentication required*. So we'll look at an example with a different API of `authentication` in `header parameters`.

                      ISS API mentions that currently there is no authentication required. So we'll look at an example with a different API of authentication in header parameters.

                      +
                      #### Request Headers

                      1.4.1.4. Request Headers¶

                      +
                      Request headers are key-value pairs sent in an HTTP request to provide information about the request itself.

                      Request headers are key-value pairs sent in an HTTP request to provide information about the request itself.

                      +
                      Some request headers are:

                      1. **Content-Type**: Specifies the media type of the resource or data. Common examples include "application/json" for JSON data, "text/html" for HTML content, and "application/xml" for XML data.

                      3. **Authorization**: Contains credentials for authenticating the client with the server, often used with tokens or other forms of authentication.

                      4. **User-Agent**: Provides information about the client (browser or other client), including its version and operating system.


                      In Python's `requests` library, you can include headers in a request by using the `headers` argument, like this:

                      ```python
                      headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR_TOKEN'}
                      response = requests.get(url, headers=headers)
                      ```

                      Some request headers are:

                      +
                        +
                      1. Content-Type: Specifies the media type of the resource or data. Common examples include "application/json" for JSON data, "text/html" for HTML content, and "application/xml" for XML data.

                        +
                      2. +
                      3. Authorization: Contains credentials for authenticating the client with the server, often used with tokens or other forms of authentication.

                        +
                      4. +
                      5. User-Agent: Provides information about the client (browser or other client), including its version and operating system.

                        +
                      6. +
                      +

                      In Python's requests library, you can include headers in a request by using the headers argument, like this:

                      +
                      headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR_TOKEN'}
                      +response = requests.get(url, headers=headers)
                      ⚠️🚨 ¡Careful with the authentication token! ⚠️🚨 A token is a personal credential for accessing an API, through which your request quota to the API is managed. Therefore, the ideal procedure is to ensure security by storing the token as a variable in an .env file. This way, you can call the environment variable without having to publicly display the token.

                      ⚠️🚨 ¡Careful with the authentication token! ⚠️🚨 A token is a personal credential for accessing an API, through which your request quota to the API is managed. Therefore, the ideal procedure is to ensure security by storing the token as a variable in an .env file. This way, you can call the environment variable without having to publicly display the token.

                      +
                      **Example: News API**

                      Example: News API

                      +
                      The *News API* lets you locate articles and breaking news headlines from news sources and blogs across the web.

                      The News API lets you locate articles and breaking news headlines from news sources and blogs across the web.

                      +
                      - Let's look at the [NewsAPI Authentication documentation](https://newsapi.org/docs/authentication). It mentions:
                      ```
                      You can attach your API key to a request in one of three ways:

                      - Via the apiKey querystring parameter.
                      - Via the X-Api-Key HTTP header.
                      - Via the Authorization HTTP header. Including Bearer is optional, and be sure not to base 64 encode it like you may have seen in other authentication tutorials.
                      We strongly recommend the either of last two so that your API key isn't visible to others in logs or via request sniffing.

                      If you don't append your API key correctly, or your API key is invalid, you will receive a 401 - Unauthorized HTTP error.
                      ```

                      - Let's look at the documentation for the endpoints to see what **parameters** they accept: [NewsAPI endpoints](https://newsapi.org/docs/endpoints). We see we have the endpoint `/v2/top-headlines` – *returns breaking news headlines for countries, categories, and singular publishers. This is perfect for use with news tickers or anywhere you want to use live up-to-date news headlines.*
                        +
                      • Let's look at the NewsAPI Authentication documentation. It mentions:
                      • +
                      +
                      You can attach your API key to a request in one of three ways:
                      +
                      +- Via the apiKey querystring parameter.
                      +- Via the X-Api-Key HTTP header.
                      +- Via the Authorization HTTP header. Including Bearer is optional, and be sure not to base 64 encode it like you may have seen in other authentication tutorials.
                      +We strongly recommend the either of last two so that your API key isn't visible to others in logs or via request sniffing.
                      +
                      +If you don't append your API key correctly, or your API key is invalid, you will receive a 401 - Unauthorized HTTP error.
                      +
                      +
                        +
                      • Let's look at the documentation for the endpoints to see what parameters they accept: NewsAPI endpoints. We see we have the endpoint /v2/top-headlines – returns breaking news headlines for countries, categories, and singular publishers. This is perfect for use with news tickers or anywhere you want to use live up-to-date news headlines. +If we look at the documentation of that endpoint we see we have more parameters we can add such as country or category.
                      • +
                      +
                      [ ]:
                      url = "http://newsapi.org/v2/top-headlines"
                      [ ]:
                      response = requests.get(url)
                      [ ]:
                      response.status_code
                      Let's try a made up key, using the apiKey querystring parameter. As we read in the documentation, this is not the recommended way.

                      Let's try a made up key, using the apiKey querystring parameter. As we read in the documentation, this is not the recommended way.

                      +
                      [ ]:
                      url = f"http://newsapi.org/v2/top-headlines?country=us&apiKey=manolonodoesnthavekey"
                      [ ]:
                      url
                      [ ]:
                      response = requests.get(url)
                      [ ]:
                      response.status_code
                      **4xx**: client error. This means, it is our error.

                      4xx: client error. This means, it is our error.

                      +
                      Lets do with a correct key. You should get your own API key through their website. Once we've saved our key, we'll send it via the X-Api-Key HTTP header.

                      Lets do with a correct key. You should get your own API key through their website. Once we've saved our key, we'll send it via the X-Api-Key HTTP header.

                      +
                      ##### Saving the API Key
                      Saving the API Key¶
                      +
                      Storing an API key directly in your code can expose sensitive information, especially if your code is publicly available (e.g., on a public GitHub repository). The best practice for saving and loading an API key in your code involves the following steps:

                      1. **Storing the API Key**:
                      - **Use Environment Variables**: Store your API key in an environment variable on your system. This keeps the key out of your codebase and allows you to change it without altering your code.


                      - **Create a .env File**: If you prefer, you can create a `.env` file (just call it `.env` nothing else before the `.`) in your project directory to store the API key. Inside this file, you would have something like:
                      ```
                      API_KEY=your-api-key-here
                      ```

                      - **Add .env to .gitignore**: If you're using a version control system like Git, make sure to add the `.env` file to your `.gitignore` file. This prevents the `.env` file (and therefore your API key) from being uploaded to any public repositories. We'll talk about Git and .gitignore in more detail soon.

                      Storing an API key directly in your code can expose sensitive information, especially if your code is publicly available (e.g., on a public GitHub repository). The best practice for saving and loading an API key in your code involves the following steps:

                      +
                        +
                      1. Storing the API Key:

                        +
                          +
                        • Use Environment Variables: Store your API key in an environment variable on your system. This keeps the key out of your codebase and allows you to change it without altering your code.

                          +
                        • +
                        • Create a .env File: If you prefer, you can create a .env file (just call it .env nothing else before the .) in your project directory to store the API key. Inside this file, you would have something like:

                          +
                          API_KEY=your-api-key-here
                          +
                          +
                            +
                          • Add .env to .gitignore: If you're using a version control system like Git, make sure to add the .env file to your .gitignore file. This prevents the .env file (and therefore your API key) from being uploaded to any public repositories. We'll talk about Git and .gitignore in more detail soon.
                          • +
                          +
                        • +
                        +
                      2. +
                      3. Load the Key in Your Code:

                        +

                        You can use libraries like python-dotenv to load the key into your code. + You would need to install python-dotenv first.

                        +
                        !pip install python-dotenv
                        +
                        +from dotenv import load_dotenv
                        +import os
                        +
                        +load_dotenv()
                        +api_key = os.getenv("API_KEY")

                        Now, api_key contains the value of your API key, and you can use it to authenticate your requests to the API.

                        +
                      4. +
                      +
                      Lets do the second approach. Lets save it in an env file and save it in a variable.

                      Lets do the second approach. Lets save it in an env file and save it in a variable.

                      +
                      `Make sure your file is named .env and not .env.txt! Is one of the most common errors. If you need, look at the document properties to make sure it doesn't have .txt at the end even if its not showing the .txt when looking at your folder.`

                      Make sure your file is named .env and not .env.txt! Is one of the most common errors. If you need, look at the document properties to make sure it doesn't have .txt at the end even if its not showing the .txt when looking at your folder.

                      +
                      [ ]:
                      #!pip install python-dotenv
                      [ ]:
                      import os
                      from dotenv import load_dotenv, find_dotenv

                      load_dotenv()

                      my_key = os.getenv("API_KEY")
                      If you get an error, make sure you saved the .env file in the same directory as your jupyter notebook. You can use `os.getcwd()` to do so.

                      If you get an error, make sure you saved the .env file in the same directory as your jupyter notebook. You can use os.getcwd() to do so.

                      +
                      [ ]:
                      os.getcwd()
                      ##### Using the API Key in the Header Parameters
                      Using the API Key in the Header Parameters¶
                      +
                      [ ]:
                      url_top_headlines = "http://newsapi.org/v2/top-headlines"
                      [ ]:
                      parameters = {"country": "us", "category": "science"}
                      [ ]:
                      headers = {"X-Api-Key": my_key}
                      [ ]:
                      response = requests.get(
                      url=url_top_headlines,
                      params=parameters,
                      headers=headers
                      )
                      [ ]:
                      response.status_code
                      [ ]:
                      data = response.json()
                      [ ]:
                      data
                      ## APIs examples

                      APIs examples¶

                      +
                      ### NewsAPI

                      1.5.1. NewsAPI¶

                      +
                      [ ]:
                      type(data) # response.json() returns a dictionary
                      [ ]:
                      data.keys() # we can get the keys with method keys()
                      [ ]:
                      data.get("status") # we can get a key's value by using get method
                      [ ]:
                      data["totalResults"] # or by using dictionary notation to access values
                      [ ]:
                      # this is the same as above
                      data.get("totalResults")
                      `dict[key]` raises error if the key does not exits
                      `dict.get(key)` does not raise an error if the key does not exist

                      dict[key] raises error if the key does not exits
                      dict.get(key) does not raise an error if the key does not exist

                      +
                      [ ]:
                      # lets look at the articles
                      articles = data.get("articles")
                      [ ]:
                      type(articles) # lets see what type it is to know how to access it
                      [ ]:
                      len(articles)
                      [ ]:
                      articles[0] # since articles is a list, we can access its elements by the index
                      [ ]:
                      type(articles[0]) # each element inside the list is a dictionary
                      [ ]:
                      [art["title"] for art in articles] # lets get all the titles using list comprehension
                      [ ]:
                      df = pd.DataFrame(articles) # lets create a dataframe with the articles
                      [ ]:
                      df.head()
                      #### 💡 Check for understanding

                      1.5.1.1. 💡 Check for understanding¶

                      +
                      1. What data type is the column *source*?
                      2. Since the column *source* is not very useful as it is, create a column called *name* that contains only the name inside the column *source*.
                      3. How many times each unique name appears in the "name" column?
                        +
                      1. What data type is the column source?
                      2. +
                      3. Since the column source is not very useful as it is, create a column called name that contains only the name inside the column source.
                      4. +
                      5. How many times each unique name appears in the "name" column?
                      6. +
                      +
                      [ ]:
                      # Your answer goes here
                      ### Pokemon API

                      1.5.2. Pokemon API¶

                      +
                      [PokeAPI](https://pokeapi.co/) is a full RESTful API linked to an extensive database detailing everything about the Pokémon main game series.

                      In the documentation we see that we can get lot's of data. Let's look at the endpoint *pokemon*: ```https://pokeapi.co/api/v2/pokemon/{id or name}/```

                      PokeAPI is a full RESTful API linked to an extensive database detailing everything about the Pokémon main game series.

                      +

                      In the documentation we see that we can get lot's of data. Let's look at the endpoint pokemon: https://pokeapi.co/api/v2/pokemon/{id or name}/

                      +
                      [ ]:
                      res=requests.get('https://pokeapi.co/api/v2/pokemon/25')
                      res.status_code
                      [ ]:
                      data=res.json()
                      data
                      [ ]:
                      type(data)
                      [ ]:
                      data.keys()
                      [ ]:
                      # Lets look at the following attributes
                      print(data["name"])
                      print(data["weight"])
                      print(data["sprites"])
                      [ ]:
                      # Lets get the pokemon image from sprites - front_default.
                      # Remember we can read the documentation to understand how to get all the information
                      data['sprites']['front_default']
                      We will define a function, `get_pokemon`, that takes an ID number as input. This function fetches details about a Pokémon from the PokeAPI using the given ID, extracts its name, weight, and front-facing image, and then returns this data in a dictionary format. If the ID is invalid or there's an issue with the request, a ValueError is raised.

                      We will define a function, get_pokemon, that takes an ID number as input. This function fetches details about a Pokémon from the PokeAPI using the given ID, extracts its name, weight, and front-facing image, and then returns this data in a dictionary format. If the ID is invalid or there's an issue with the request, a ValueError is raised.

                      +
                      [ ]:
                      def get_pokemon(id_number):
                      res=requests.get('https://pokeapi.co/api/v2/pokemon/{}'.format(id_number))
                      if(res.status_code==200):
                      data = res.json()
                      return {"id": id_number,'name':data['name'], "weight":data["weight"],'image':data['sprites']['front_default']}
                      else:
                      raise ValueError("Cannot get pokemon, id does not exist")

                      [ ]:
                      get_pokemon(25) # Lets use the function for pokemon id 25
                      [ ]:
                      [get_pokemon(i+1) for i in range(5)] # Lets use list comprehension to gather information for the first 5 pokemons
                      We will now define a function, `print_pokemon`, that takes in a Pokémon dictionary and displays its ID, name, weight, and its image.

                      We will now define a function, print_pokemon, that takes in a Pokémon dictionary and displays its ID, name, weight, and its image.

                      +
                      [ ]:
                      from IPython.display import Image

                      def print_pokemon(poke):
                      print(str(poke["id"])+" " +poke["name"]+ " "+str(poke["weight"]) + " kg")
                      display(Image(url=poke['image']))
                      After defining this function, we will retrieve and display details for the first 10 Pokémon by iterating through their IDs and using both the `get_pokemon` and `print_pokemon` functions.

                      After defining this function, we will retrieve and display details for the first 10 Pokémon by iterating through their IDs and using both the get_pokemon and print_pokemon functions.

                      +
                      [ ]:
                      [print_pokemon(get_pokemon(i+1)) for i in range(10)]
                      #### json_normalize()

                      1.5.2.1. json_normalize()¶

                      +
                      Want the data as a DataFrame instead of a dictionary?

                      Want the data as a DataFrame instead of a dictionary?

                      +
                      The following line creates a DataFrame from the items of the dictionary data. Each key-value pair will be treated as a row, with two columns: the first for the key and the second for the value.

                      The following line creates a DataFrame from the items of the dictionary data. Each key-value pair will be treated as a row, with two columns: the first for the key and the second for the value.

                      +
                      [ ]:
                      df = pd.DataFrame(data.items())
                      df
                      That format is still very hard to work with. Lets use `json_normalize` instead. `json_normalize` is used to normalize semi-structured JSON data into a flat table (DataFrame). It's particularly useful when dealing with nested dictionaries or lists inside JSON.

                      That format is still very hard to work with. Lets use json_normalize instead. json_normalize is used to normalize semi-structured JSON data into a flat table (DataFrame). It's particularly useful when dealing with nested dictionaries or lists inside JSON.

                      +
                      [ ]:
                      df = pd.json_normalize(data)
                      df
                      A lot better, even though we still have nested dictionaries or lists inside the dataframe.

                      Let's see again the difference between creating a dataframe directly or using `json_normalize` by using `data["abilities"]`.

                      A lot better, even though we still have nested dictionaries or lists inside the dataframe.

                      +

                      Let's see again the difference between creating a dataframe directly or using json_normalize by using data["abilities"].

                      +
                      [ ]:
                      pd.DataFrame(data["abilities"])
                      [ ]:
                      pd.json_normalize(data["abilities"])
                      ### Coincap API

                      1.5.3. Coincap API¶

                      +
                      CoinCap is a useful tool for real-time pricing and market activity for over 1,000 cryptocurrencies. Check the documentation at https://docs.coincap.io/

                      CoinCap is a useful tool for real-time pricing and market activity for over 1,000 cryptocurrencies. Check the documentation at https://docs.coincap.io/

                      +
                      [ ]:
                      url = "http://api.coincap.io/v2/assets"
                      [ ]:
                      response = requests.get(url)
                      [ ]:
                      print(response)
                      [ ]:
                      res = response.json()
                      [ ]:
                      res # it returns a dictionary with all the data
                      [ ]:
                      # Again, let's try to have it as a dataframe
                      data = pd.DataFrame(res["data"])
                      data.head()
                      [ ]:
                      # Lets see if the result would differ in this case using json_normalize
                      pd.json_normalize(res["data"])
                      ### Api Jokes

                      JokeAPI is a REST API that serves uniformly and well formatted jokes.
                      It can be used without any API token, membership, registration or payment.
                      It supports a variety of filters that can be applied to get just the right jokes you need.
                      Check the documentation here: https://jokeapi.dev/

                      1.5.4. Api Jokes¶

                      +

                      JokeAPI is a REST API that serves uniformly and well formatted jokes. +It can be used without any API token, membership, registration or payment. +It supports a variety of filters that can be applied to get just the right jokes you need. +Check the documentation here: https://jokeapi.dev/

                      +
                      [ ]:
                      url_random_joke = "https://v2.jokeapi.dev/joke/dark?amount=3"
                      request = requests.get(url_random_joke).json()
                      request
                      ### 💡 Check for understanding

                      1.5.5. 💡 Check for understanding¶

                      +
                      Choose one API from the [Public APIs list](https://github.com/public-apis/public-apis). Attempt to call your selected API, either with or without parameters, and retrieve some valuable information. Document your findings.

                      Choose one API from the Public APIs list. Attempt to call your selected API, either with or without parameters, and retrieve some valuable information. Document your findings.

                      +
                      [ ]:
                      # Your answer goes here
                      ## API Wrappers

                      A Python wrapper is a Python library or module that provides a more convenient or more "Pythonic" interface to another software component, such as a library in another language, a system tool, or a web API. It "wraps" the functionality of that component in a way that abstracts away its complexities and makes it easier to use in a Python context. 🙌🏻

                      One example is the `tweepy` library, which makes obtaining data from Twitter's API relatively straightforward.

                      API Wrappers¶

                      +

                      A Python wrapper is a Python library or module that provides a more convenient or more "Pythonic" interface to another software component, such as a library in another language, a system tool, or a web API. It "wraps" the functionality of that component in a way that abstracts away its complexities and makes it easier to use in a Python context. 🙌🏻

                      +

                      One example is the tweepy library, which makes obtaining data from Twitter's API relatively straightforward.

                      +
                      ## Summary
                      - Import the `requests` library.
                      - Store the necessary credentials, such as API key or OAuth tokens, if the API requires them.
                      - Execute a `request.get` to the desired API endpoint (the API's documentation will provide details on available endpoints).
                      - The API returns a JSON response.
                      - This JSON can be converted into a dataframe, or you can further explore its elements (typically a list of dictionaries).

                      Summary¶

                      +
                        +
                      • Import the requests library.
                      • +
                      • Store the necessary credentials, such as API key or OAuth tokens, if the API requires them.
                      • +
                      • Execute a request.get to the desired API endpoint (the API's documentation will provide details on available endpoints).
                      • +
                      • The API returns a JSON response.
                      • +
                      • This JSON can be converted into a dataframe, or you can further explore its elements (typically a list of dictionaries).
                      • +
                      +
                      ## Glossary

                      1.8. Glossary¶

                      +
                      * DNS: domain name system.
                      * HTTP: is the protocol used to transfer data over the web.
                      * API: application programming interface.
                      * REST: series of rules, architectural style.
                        +
                      • DNS: domain name system.
                      • +
                      • HTTP: is the protocol used to transfer data over the web.
                      • +
                      • API: application programming interface.
                      • +
                      • REST: series of rules, architectural style.
                      • +
                      +
                      * `requests`: Python library for interacting with APIs.
                      * URL: server name you want to ask information for.
                      * endpoint: server service you want to ask information for.
                      * parameters: extra parameters to your query.
                      * headers: metadata, invisible information.
                        +
                      • requests: Python library for interacting with APIs.
                      • +
                      • URL: server name you want to ask information for.
                      • +
                      • endpoint: server service you want to ask information for.
                      • +
                      • parameters: extra parameters to your query.
                      • +
                      • headers: metadata, invisible information.
                      • +
                      +
                      ## Further materials

                      1.9. Further materials¶

                      +
                      [5 Simple-To-Use APIs for Beginners](https://dev.to/alanconstantino/5-simple-to-use-apis-for-beginners-2e0n)

                      5 Simple-To-Use APIs for Beginners

                      +
                      [RapidAPI](https://rapidapi.com/category/Sports): access thousands of APIs.

                      RapidAPI: access thousands of APIs.

                      +
                        [4]:
                        %pip install requests
                        mambajs 0.21.1
                        +
                        +Process pip requirements ...
                        +
                        +Requirement requests already satisfied.
                        +
                        [5]:
                        import requests
                        [ ]:

                        Notebook
                        Python 3.13 (XPython)
                        Kernel status: Idle
                          [ ]:
                          # Lab | Error Handling
                          [ ]:
                          # Exercise 1 - Division

                          [2]:
                          # Exercise 1 - Division

                          def divide_numbers(a, b):
                          try:
                          result = a / b

                          except ZeroDivisionError:
                          print("Error: Cannot divide by zero.")

                          except TypeError:
                          print("Error: Both inputs must be numbers.")

                          else:
                          print(f"Result: {result}")

                          finally:
                          print("Division operation completed.")
                          [5]:
                          divide_numbers(20, 2)
                          divide_numbers(10, 0)
                          divide_numbers(10, "2")
                          Result: 10.0
                          +Division operation completed.
                          +Error: Cannot divide by zero.
                          +Division operation completed.
                          +Error: Both inputs must be numbers.
                          +Division operation completed.
                          +
                          [6]:
                          # Exercise 2 - List Access

                          def get_list_element(lst, index):
                          try:
                          element = lst[index]

                          except IndexError:
                          print("Error: Index out of range.")

                          except TypeError:
                          print("Error: Invalid index type.")

                          else:
                          print(f"Element found: {element}")

                          finally:
                          print("List access completed.")
                          [9]:
                          get_list_element([1, 2, 3], 1)
                          get_list_element([1, 2, 3], 10)
                          Element found: 2
                          +List access completed.
                          +Error: Index out of range.
                          +List access completed.
                          +
                          [ ]:
                          # Exercise 3 - Dictionary Lookup
                          [10]:

                          def get_dictionary_value(dictionary, key):
                          try:
                          value = dictionary[key]

                          except KeyError:
                          print("Error: Key not found.")

                          else:
                          print(f"Value: {value}")

                          finally:
                          print("Dictionary lookup completed.")
                          [11]:
                          student = {
                          "name": "Bob",
                          "age": 30
                          }

                          get_dictionary_value(student, "name")
                          get_dictionary_value(student, "city")
                          Value: Alice
                          +Dictionary lookup completed.
                          +Error: Key not found.
                          +Dictionary lookup completed.
                          +
                          [ ]:
                          # Exercise 4 - User Age Validation with raise
                          [17]:
                          def validate_age(age):
                          try:
                          if age < 0:
                          raise ValueError("Age cannot be negative.")

                          if age > 120:
                          confirmation = input("Are you sure? (yes/no): ")

                          if confirmation.lower() != "yes":
                          raise ValueError("Age validation cancelled.")

                          except ValueError as e:
                          print(f"Error: {e}")

                          else:
                          print(f"Age entered: {age}")

                          finally:
                          print("Age validation completed.")
                          [18]:
                          validate_age(5)
                          validate_age(18)
                          validate_age(1001)
                          validate_age(-5)
                          Age entered: 5
                          +Age validation completed.
                          +Age entered: 18
                          +Age validation completed.
                          +
                          Are you sure? (yes/no):  yes
                          +
                          Age entered: 1001
                          +Age validation completed.
                          +Error: Age cannot be negative.
                          +Age validation completed.
                          +
                          [ ]:

                            [ ]:
                            #Challenge 1 - Katas
                            [3]:
                            members = [[18, 20], [45, 2], [61, 12], [37, 6], [21, 21], [78, 9]]

                            categories = []

                            for age, handicap in members:
                            if age >= 55 and handicap > 7:
                            categories.append("Senior")
                            else:
                            categories.append("Open")

                            print(categories)
                            ['Open', 'Open', 'Senior', 'Open', 'Open', 'Senior']
                            +
                            [ ]:
                            #Challenge 2 - Katas
                            [6]:
                            def sum_multiples(number):

                            if number < 0:
                            return 0

                            total = 0

                            for x in range(number):
                            if x % 3 == 0 or x % 5 == 0:
                            total += x

                            return total


                            print(sum_multiples(10))
                            23
                            +
                            [8]:
                            number = 654321

                            digits = [int(digit) for digit in str(number)]

                            print(digits)
                            [6, 5, 4, 3, 2, 1]
                            +
                            [9]:
                            def invert(numbers):
                            return [-number for number in numbers]

                            print(invert([1, 2, 3, 4, 5]))
                            print(invert([1, -2, 3, -4, 5]))
                            print(invert([]))
                            [-1, -2, -3, -4, -5]
                            +[-1, 2, -3, 4, -5]
                            +[]
                            +
                            [ ]:
                            # Exercice 1
                            [12]:
                            squares = {x: x**2 for x in range(1, 16)}

                            print(squares)
                            {1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100, 11: 121, 12: 144, 13: 169, 14: 196, 15: 225}
                            +
                            [ ]:
                            # Exercice 2
                            [13]:
                            list_a = [5, 6]
                            list_b = [3, 4]

                            pairs = [(x, y) for x in list_a for y in list_b]

                            print(pairs)
                            [(5, 3), (5, 4), (6, 3), (6, 4)]
                            +
                            [ ]:
                            # Exercice 3
                            [14]:
                            data = {"a": [1, 2], "b": [3, 4]}

                            result = [(key, value)
                            for key, values in data.items()
                            for value in values]

                            print(result)
                            [('a', 1), ('a', 2), ('b', 3), ('b', 4)]
                            +
                            [ ]:
                            # Bonus exercises
                            # Exercise 1
                            [15]:
                            data = [
                            ([10, 20], [30, 40]),
                            ([50, 60], [70, 80])
                            ]

                            result = [
                            (x, y)
                            for list1, list2 in data
                            for x in list1
                            for y in list2
                            ]

                            print(result)
                            [(10, 30), (10, 40), (20, 30), (20, 40), (50, 70), (50, 80), (60, 70), (60, 80)]
                            +
                            [ ]:
                            # Exe2cise 2
                            [16]:
                            strings = ["wx", "yz"]

                            result = {
                            (char1, char2)
                            for char1 in strings[0]
                            for char2 in strings[1]
                            }

                            print(result)
                            {('w', 'y'), ('x', 'z'), ('x', 'y'), ('w', 'z')}
                            +
                            [ ]:

                              [ ]:

                              9
                              1
                              2
                              NEWS_API_KEY=ebbbc1c1b02b4a7cb5711d6674bb360c
                              OPENAI_API_KEY=ebbbc1c1b02b4a7cb5711d6674bb360c
                              • lab-python-data-structures.ipynb
                              • Untitled16.ipynb
                              • Untitled17.ipynb
                              • Untitled20.ipynb
                              • Untitled21.ipynb
                              • Project_shark_DF team.ipynb
                              • Untitled30.ipynb
                              • Untitled34.ipynb
                              • Untitled35.ipynb
                              • Challenge Katas.ipynb
                              • lab_error_handling_exercices.ipynb
                              • Untitled38.ipynb
                              • 2.5_apis.ipynb
                              • .env.txt
                              • Untitled41.ipynb
                              Notebook
                              Python (Pyodide)
                              Kernel status: Idle
                                [ ]:
                                # CFU | Data Types
                                # Working with Data Types
                                [ ]:

                                Common tools
                                No metadata.
                                Advanced Tools
                                No metadata.
                                  19
                                  none
                                  Python 3.13 (XPython) | Idle
                                  Uploading…

                                  Mode: Command
                                  Ln 1, Col 1
                                  Spaces: 4
                                  lab_error_handling_exercices.ipynb
                                  0
                                  Notebook
                                  Python 3.13 (XPython)
                                  Kernel status: Idle
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Notebook
                                  Python 3.13 (XPython)
                                  Kernel status: Idle
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Notebook
                                  Python 3.13 (XPython)
                                  Kernel status: Idle
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Notebook
                                  Python 3.13 (XPython)
                                  Kernel status: Idle
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Notebook
                                  Python (Pyodide)
                                  Kernel status: Idle
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Notebook
                                  Python 3.13 (XPython)
                                  Kernel status: Idle
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Notebook
                                  Python 3.13 (XPython)
                                  Kernel status: Idle
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Notebook
                                  Python 3.13 (XPython)
                                  Kernel status: Idle
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Notebook
                                  Python 3.13 (XPython)
                                  Kernel status: Idle
                                  Alt+[
                                  Alt+]
                                  Tab
                                  Alt+[
                                  Alt+]
                                  Tab
                                    Alt+[
                                    Alt+]
                                    Tab
                                    \ No newline at end of file