diff --git a/README.md b/README.md index f1d72de6355..9f594232a74 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,12 @@ No known bugs at this time. ## Authors Alexa Orrico - [Github](https://github.com/alexaorrico) / [Twitter](https://twitter.com/alexa_orrico) Jennifer Huang - [Github](https://github.com/jhuang10123) / [Twitter](https://twitter.com/earthtojhuang) +Ibrahim Tijani - [Github](https://github.com/teebabs521) / [Twitter](https://twitter.com/teejayibrahim) +## What's New in v3 by Ibrahim Tijani? +- Improved API functionality +- Optimized database queries +- Bug fixes and performance improvements Second part of Airbnb: Joann Vuong ## License Public Domain. No copy write protection. diff --git a/__pycache__/console.cpython-34.pyc b/__pycache__/console.cpython-34.pyc new file mode 100644 index 00000000000..db2513e6ba3 Binary files /dev/null and b/__pycache__/console.cpython-34.pyc differ diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/api/v1/__init__.py b/api/v1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/api/v1/app.py b/api/v1/app.py new file mode 100644 index 00000000000..c68d42d826c --- /dev/null +++ b/api/v1/app.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +"""Flask App for the AirBnB API""" +from flask import Flask, jsonify +from models import storage +from api.v1.views import app_views +import os + +app = Flask(__name__) + +# Register the Blueprint +app.register_blueprint(app_views) + +@app.teardown_appcontext +def teardown_db(exception): + """Closes storage session""" + storage.close() + +if __name__ == "__main__": + host = os.getenv("HBNB_API_HOST", "0.0.0.0") + port = int(os.getenv("HBNB_API_PORT", 5000)) + app.run(host=host, port=port, threaded=True) + diff --git a/api/v1/views/__init__.py b/api/v1/views/__init__.py new file mode 100644 index 00000000000..156fbd3cfc3 --- /dev/null +++ b/api/v1/views/__init__.py @@ -0,0 +1,10 @@ +#!/usr/bin/python3 +"""Blueprint for API""" +from flask import Blueprint + +app_views = Blueprint("app_views", __name__, url_prefix="/api/v1") + +# Import all views (PEP8 will complain, ignore it) +from api.v1.views.index import * + + diff --git a/api/v1/views/index.py b/api/v1/views/index.py new file mode 100644 index 00000000000..6ab232dfc9e --- /dev/null +++ b/api/v1/views/index.py @@ -0,0 +1,28 @@ +#!/usr/bin/python3 +"""Index route for API""" +from flask import jsonify +from api.v1.views import app_views + +@app_views.route('/status', methods=['GET'], strict_slashes=False) +def status(): + """Returns the API status""" + return jsonify({"status": "OK"}) + + +"""Index route for API""" +from flask import jsonify +from api.v1.views import app_views +from models import storage + +@app_views.route('/stats', methods=['GET'], strict_slashes=False) +def stats(): + """Retrieve the number of each object by type""" + stats = { + "amenities": storage.count("Amenity"), + "cities": storage.count("City"), + "places": storage.count("Place"), + "reviews": storage.count("Review"), + "states": storage.count("State"), + "users": storage.count("User") + } + return jsonify(stats) diff --git a/file.json b/file.json new file mode 100644 index 00000000000..879ecb33d86 --- /dev/null +++ b/file.json @@ -0,0 +1 @@ +{"Place.f7a17a63-208e-49ad-a40e-708874c1c93c": {"updated_at": "2025-02-16T11:06:50.539769", "created_at": "2025-02-16T11:06:50.539769", "id": "f7a17a63-208e-49ad-a40e-708874c1c93c", "__class__": "Place"}, "Amenity.7ccabb88-435f-4d15-a774-c6b71bbc4050": {"updated_at": "2025-02-16T11:06:50.539734", "created_at": "2025-02-16T11:06:50.539734", "id": "7ccabb88-435f-4d15-a774-c6b71bbc4050", "__class__": "Amenity"}, "Review.23c00ba9-920f-4ed5-be9a-c52a27a289a1": {"updated_at": "2025-02-16T11:06:50.539685", "created_at": "2025-02-16T11:06:50.539685", "id": "23c00ba9-920f-4ed5-be9a-c52a27a289a1", "__class__": "Review"}, "City.3284d804-24a2-44f3-81c0-7094c2c9ba07": {"updated_at": "2025-02-16T11:06:50.539703", "created_at": "2025-02-16T11:06:50.539703", "id": "3284d804-24a2-44f3-81c0-7094c2c9ba07", "__class__": "City"}, "State.e7288338-429e-4d3e-bc52-7e2b6aa86972": {"updated_at": "2025-02-16T11:06:50.539719", "created_at": "2025-02-16T11:06:50.539719", "id": "e7288338-429e-4d3e-bc52-7e2b6aa86972", "__class__": "State"}, "User.c52fbd04-a9ed-4e0e-88c3-abaf453ad041": {"updated_at": "2025-02-16T11:06:50.539749", "created_at": "2025-02-16T11:06:50.539749", "id": "c52fbd04-a9ed-4e0e-88c3-abaf453ad041", "__class__": "User"}, "BaseModel.22d5bb1c-57d9-409e-be54-220b1def189e": {"created_at": "2025-02-16T11:06:50.539663", "id": "22d5bb1c-57d9-409e-be54-220b1def189e", "updated_at": "2025-02-16T11:06:50.539663", "__class__": "BaseModel"}} \ No newline at end of file diff --git a/models/__pycache__/__init__.cpython-34.pyc b/models/__pycache__/__init__.cpython-34.pyc new file mode 100644 index 00000000000..f3eb94a25fa Binary files /dev/null and b/models/__pycache__/__init__.cpython-34.pyc differ diff --git a/models/__pycache__/amenity.cpython-34.pyc b/models/__pycache__/amenity.cpython-34.pyc new file mode 100644 index 00000000000..5812b277ae5 Binary files /dev/null and b/models/__pycache__/amenity.cpython-34.pyc differ diff --git a/models/__pycache__/base_model.cpython-34.pyc b/models/__pycache__/base_model.cpython-34.pyc new file mode 100644 index 00000000000..ac64269949e Binary files /dev/null and b/models/__pycache__/base_model.cpython-34.pyc differ diff --git a/models/__pycache__/city.cpython-34.pyc b/models/__pycache__/city.cpython-34.pyc new file mode 100644 index 00000000000..caf5c30dfb8 Binary files /dev/null and b/models/__pycache__/city.cpython-34.pyc differ diff --git a/models/__pycache__/place.cpython-34.pyc b/models/__pycache__/place.cpython-34.pyc new file mode 100644 index 00000000000..02a5378cc67 Binary files /dev/null and b/models/__pycache__/place.cpython-34.pyc differ diff --git a/models/__pycache__/review.cpython-34.pyc b/models/__pycache__/review.cpython-34.pyc new file mode 100644 index 00000000000..77b1c62a14d Binary files /dev/null and b/models/__pycache__/review.cpython-34.pyc differ diff --git a/models/__pycache__/state.cpython-34.pyc b/models/__pycache__/state.cpython-34.pyc new file mode 100644 index 00000000000..40b722d6632 Binary files /dev/null and b/models/__pycache__/state.cpython-34.pyc differ diff --git a/models/__pycache__/user.cpython-34.pyc b/models/__pycache__/user.cpython-34.pyc new file mode 100644 index 00000000000..b34adf5a6dd Binary files /dev/null and b/models/__pycache__/user.cpython-34.pyc differ diff --git a/models/engine/__pycache__/__init__.cpython-34.pyc b/models/engine/__pycache__/__init__.cpython-34.pyc new file mode 100644 index 00000000000..a5edda43290 Binary files /dev/null and b/models/engine/__pycache__/__init__.cpython-34.pyc differ diff --git a/models/engine/__pycache__/db_storage.cpython-34.pyc b/models/engine/__pycache__/db_storage.cpython-34.pyc new file mode 100644 index 00000000000..cea42f5ed9a Binary files /dev/null and b/models/engine/__pycache__/db_storage.cpython-34.pyc differ diff --git a/models/engine/__pycache__/file_storage.cpython-34.pyc b/models/engine/__pycache__/file_storage.cpython-34.pyc new file mode 100644 index 00000000000..648111a00aa Binary files /dev/null and b/models/engine/__pycache__/file_storage.cpython-34.pyc differ diff --git a/models/engine/db_storage.py b/models/engine/db_storage.py index b8e7d291e6f..f3b4fe1715c 100755 --- a/models/engine/db_storage.py +++ b/models/engine/db_storage.py @@ -74,3 +74,74 @@ def reload(self): def close(self): """call remove() method on the private session attribute""" self.__session.remove() + + + #!/usr/bin/python3 +"""Database Storage""" +from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy import create_engine +from models.base_model import Base +from models.user import User +from models.state import State +from models.city import City +from models.amenity import Amenity +from models.place import Place +from models.review import Review +import os + + +class DBStorage: + """Database storage engine""" + __engine = None + __session = None + + def __init__(self): + """Initialize the engine""" + self.__engine = create_engine('mysql+mysqldb://{}:{}@{}/{}'.format( + os.getenv('HBNB_MYSQL_USER'), + os.getenv('HBNB_MYSQL_PWD'), + os.getenv('HBNB_MYSQL_HOST'), + os.getenv('HBNB_MYSQL_DB')), + pool_pre_ping=True) + + if os.getenv('HBNB_ENV') == 'test': + Base.metadata.drop_all(self.__engine) + + def all(self, cls=None): + """Query all objects or specific class""" + if cls: + return {obj.id: obj for obj in self.__session.query(cls).all()} + else: + objects = {} + for cls in [State, City, User, Place, Review, Amenity]: + for obj in self.__session.query(cls).all(): + objects[obj.id] = obj + return objects + + def new(self, obj): + """Add new object""" + self.__session.add(obj) + + def save(self): + """Commit changes to database""" + self.__session.commit() + + def delete(self, obj=None): + """Delete an object""" + if obj: + self.__session.delete(obj) + + def reload(self): + """Reload session""" + Base.metadata.create_all(self.__engine) + session_factory = sessionmaker(bind=self.__engine, expire_on_commit=False) + self.__session = scoped_session(session_factory) + + def get(self, cls, id): + """Retrieve one object by class and ID""" + return self.__session.query(cls).filter_by(id=id).first() + + def count(self, cls=None): + """Count number of objects in storage""" + return len(self.all(cls)) + diff --git a/models/engine/file_storage.py b/models/engine/file_storage.py index c8cb8c1764d..d139ca68aba 100755 --- a/models/engine/file_storage.py +++ b/models/engine/file_storage.py @@ -68,3 +68,57 @@ def delete(self, obj=None): def close(self): """call reload() method for deserializing the JSON file to objects""" self.reload() + + + #!/usr/bin/python3 +"""File Storage""" +import json +from models.base_model import BaseModel +from models.user import User +from models.state import State +from models.city import City +from models.amenity import Amenity +from models.place import Place +from models.review import Review + + +class FileStorage: + """Serializes instances to a JSON file & deserializes back to instances""" + __file_path = "file.json" + __objects = {} + + def all(self, cls=None): + """Returns the dictionary __objects""" + if cls is None: + return self.__objects + return {key: val for key, val in self.__objects.items() if isinstance(val, cls)} + + def new(self, obj): + """Sets in __objects the obj with key .id""" + self.__objects["{}.{}".format(obj.__class__.__name__, obj.id)] = obj + + def save(self): + """Serializes __objects to the JSON file (path: __file_path)""" + obj_dict = {key: val.to_dict() for key, val in self.__objects.items()} + with open(self.__file_path, "w") as f: + json.dump(obj_dict, f) + + def reload(self): + """Deserializes the JSON file to __objects""" + try: + with open(self.__file_path, "r") as f: + obj_dict = json.load(f) + for key, val in obj_dict.items(): + class_name = val["__class__"] + self.__objects[key] = eval(class_name)(**val) + except FileNotFoundError: + pass + + def get(self, cls, id): + """Retrieve one object by class and ID""" + return self.__objects.get("{}.{}".format(cls.__name__, id), None) + + def count(self, cls=None): + """Count number of objects in storage""" + return len(self.all(cls)) + diff --git a/tests/__pycache__/__init__.cpython-34.pyc b/tests/__pycache__/__init__.cpython-34.pyc new file mode 100644 index 00000000000..35882c48f0d Binary files /dev/null and b/tests/__pycache__/__init__.cpython-34.pyc differ diff --git a/tests/__pycache__/test_console.cpython-34.pyc b/tests/__pycache__/test_console.cpython-34.pyc new file mode 100644 index 00000000000..64d6f7dff74 Binary files /dev/null and b/tests/__pycache__/test_console.cpython-34.pyc differ diff --git a/tests/test_api/test_states.py b/tests/test_api/test_states.py new file mode 100644 index 00000000000..23ced347891 --- /dev/null +++ b/tests/test_api/test_states.py @@ -0,0 +1,18 @@ +import unittest +import json +from api.v1.app import app + +class TestStateAPI(unittest.TestCase): + def setUp(self): + """Set up test client""" + self.client = app.test_client() + + def test_get_states(self): + """Test GET request to /api/v1/states""" + response = self.client.get('/api/v1/states') + self.assertEqual(response.status_code, 200) + self.assertIsInstance(response.json, list) + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/test_models/__pycache__/__init__.cpython-34.pyc b/tests/test_models/__pycache__/__init__.cpython-34.pyc new file mode 100644 index 00000000000..52ef5aafcaf Binary files /dev/null and b/tests/test_models/__pycache__/__init__.cpython-34.pyc differ diff --git a/tests/test_models/__pycache__/test_amenity.cpython-34.pyc b/tests/test_models/__pycache__/test_amenity.cpython-34.pyc new file mode 100644 index 00000000000..fd84c18fa2e Binary files /dev/null and b/tests/test_models/__pycache__/test_amenity.cpython-34.pyc differ diff --git a/tests/test_models/__pycache__/test_base_model.cpython-34.pyc b/tests/test_models/__pycache__/test_base_model.cpython-34.pyc new file mode 100644 index 00000000000..590ef0f13a7 Binary files /dev/null and b/tests/test_models/__pycache__/test_base_model.cpython-34.pyc differ diff --git a/tests/test_models/__pycache__/test_city.cpython-34.pyc b/tests/test_models/__pycache__/test_city.cpython-34.pyc new file mode 100644 index 00000000000..f40f8b08f5f Binary files /dev/null and b/tests/test_models/__pycache__/test_city.cpython-34.pyc differ diff --git a/tests/test_models/__pycache__/test_place.cpython-34.pyc b/tests/test_models/__pycache__/test_place.cpython-34.pyc new file mode 100644 index 00000000000..e16225b711f Binary files /dev/null and b/tests/test_models/__pycache__/test_place.cpython-34.pyc differ diff --git a/tests/test_models/__pycache__/test_review.cpython-34.pyc b/tests/test_models/__pycache__/test_review.cpython-34.pyc new file mode 100644 index 00000000000..7bf7e190e52 Binary files /dev/null and b/tests/test_models/__pycache__/test_review.cpython-34.pyc differ diff --git a/tests/test_models/__pycache__/test_state.cpython-34.pyc b/tests/test_models/__pycache__/test_state.cpython-34.pyc new file mode 100644 index 00000000000..20c60cf31bf Binary files /dev/null and b/tests/test_models/__pycache__/test_state.cpython-34.pyc differ diff --git a/tests/test_models/__pycache__/test_user.cpython-34.pyc b/tests/test_models/__pycache__/test_user.cpython-34.pyc new file mode 100644 index 00000000000..f2fa54c6c3e Binary files /dev/null and b/tests/test_models/__pycache__/test_user.cpython-34.pyc differ diff --git a/tests/test_models/test_engine/__pycache__/__init__.cpython-34.pyc b/tests/test_models/test_engine/__pycache__/__init__.cpython-34.pyc new file mode 100644 index 00000000000..d1366573b7b Binary files /dev/null and b/tests/test_models/test_engine/__pycache__/__init__.cpython-34.pyc differ diff --git a/tests/test_models/test_engine/__pycache__/test_db_storage.cpython-34.pyc b/tests/test_models/test_engine/__pycache__/test_db_storage.cpython-34.pyc new file mode 100644 index 00000000000..872ac3f9837 Binary files /dev/null and b/tests/test_models/test_engine/__pycache__/test_db_storage.cpython-34.pyc differ diff --git a/tests/test_models/test_engine/__pycache__/test_file_storage.cpython-34.pyc b/tests/test_models/test_engine/__pycache__/test_file_storage.cpython-34.pyc new file mode 100644 index 00000000000..13403aeaf07 Binary files /dev/null and b/tests/test_models/test_engine/__pycache__/test_file_storage.cpython-34.pyc differ diff --git a/tests/test_models/test_engine/test_db_storage.py b/tests/test_models/test_engine/test_db_storage.py index 766e625b5af..7c5ad5d451c 100755 --- a/tests/test_models/test_engine/test_db_storage.py +++ b/tests/test_models/test_engine/test_db_storage.py @@ -86,3 +86,39 @@ def test_new(self): @unittest.skipIf(models.storage_t != 'db', "not testing db storage") def test_save(self): """Test that save properly saves objects to file.json""" + + #!/usr/bin/python3 +"""Unittest for DBStorage""" +import unittest +from models.engine.db_storage import DBStorage +from models.state import State +from models import storage + + +class TestDBStorage(unittest.TestCase): + """Test the DBStorage class""" + + def setUp(self): + """Set up test environment""" + self.storage = DBStorage() + self.storage.reload() + self.state = State(name="Nevada") + self.storage.new(self.state) + self.storage.save() + + def test_get(self): + """Test retrieving an object""" + obj = self.storage.get(State, self.state.id) + self.assertEqual(obj, self.state) + + def test_count(self): + """Test counting objects""" + count = self.storage.count() + self.assertGreater(count, 0) + state_count = self.storage.count(State) + self.assertGreaterEqual(state_count, 1) + + +if __name__ == "__main__": + unittest.main() + diff --git a/tests/test_models/test_engine/test_file_storage.py b/tests/test_models/test_engine/test_file_storage.py index 1474a34fec0..7b6697dddf9 100755 --- a/tests/test_models/test_engine/test_file_storage.py +++ b/tests/test_models/test_engine/test_file_storage.py @@ -113,3 +113,40 @@ def test_save(self): with open("file.json", "r") as f: js = f.read() self.assertEqual(json.loads(string), json.loads(js)) + + + + + #!/usr/bin/python3 +"""Unittest for FileStorage""" +import unittest +from models.engine.file_storage import FileStorage +from models.state import State + + +class TestFileStorage(unittest.TestCase): + """Test the FileStorage class""" + + def setUp(self): + """Set up test environment""" + self.storage = FileStorage() + self.state = State(name="California") + self.storage.new(self.state) + self.storage.save() + + def test_get(self): + """Test retrieving an object""" + obj = self.storage.get(State, self.state.id) + self.assertEqual(obj, self.state) + + def test_count(self): + """Test counting objects""" + count = self.storage.count() + self.assertGreater(count, 0) + state_count = self.storage.count(State) + self.assertGreaterEqual(state_count, 1) + + +if __name__ == "__main__": + unittest.main() +