Structured Project Code Repository

Python Code Nemesis
Code Like A Girl
Published in
20 min readMar 30, 2023

--

Photo by Chris Ried on Unsplash

Structured code is a programming concept that refers to a codebase that has a clear and well-defined organization, with different parts of the code serving specific purposes and interacting with one another in a well-defined way. Structured code typically has a clear separation of concerns, with different parts of the codebase dedicated to different functions, such as user interfaces, data storage, business logic, and so on.

Structured code can be achieved through the use of various programming concepts and tools, such as modules, classes, functions, and namespaces. By using these tools effectively, developers can create code that is easier to understand, modify, and maintain. A well-structured codebase can also be more scalable and reusable, allowing developers to build more complex applications with less effort and fewer errors.

Structured code is important for several reasons:

Readability

Structured code is easier to read and understand for developers. With a clear and consistent structure, code can be quickly comprehended and modified as needed. This is especially important in collaborative projects, where multiple developers are working on the same codebase.

Maintainability

Structured code is easier to maintain in the long term. With a clear separation of concerns and a well-defined structure, changes can be made to specific parts of the codebase without affecting other areas. This reduces the risk of introducing bugs or breaking existing functionality.

Scalability

Structured code is more scalable than unstructured code. As a project grows in complexity, a well-organized structure can help prevent the codebase from becoming unmanageable. This allows developers to add new features or make changes to the codebase without introducing unnecessary complexity.

Reusability

Structured code is more reusable than unstructured code. With a clear separation of concerns, individual components of the codebase can be reused in other parts of the project or in other projects altogether. This reduces the amount of duplicate code and improves overall code quality.

Example Code Repository Structure

Here’s an example code repository structure:

my-project/
README.md
LICENSE
.gitignore
requirements.txt
app/
__init__.py
config.py
models/
__init__.py
user.py
post.py
repositories/
__init__.py
user_repository.py
post_repository.py
controllers/
__init__.py
user_controller.py
post_controller.py
views/
__init__.py
user_view.py
post_view.py
templates/
base.html
user.html
post.html
static/
css/
main.css
js/
main.js
tests/
__init__.py
test_user.py
test_post.py
docs/
index.md
user.md
post.md

In this structure, the main project directory is named my-project. The root directory contains a README.md file, a LICENSE file, a .gitignore file, and a requirements.txt file, which specifies the required dependencies for the project.

The app directory contains the main application code, including a config.py file for storing configuration settings, a models directory for storing data models, a views directory for storing the application's views, a templates directory for storing HTML templates, and a static directory for storing static files like CSS and JavaScript.

The tests directory contains unit tests for the application, while the docs directory contains documentation for the project.

The repository and controller layers are responsible for handling the business logic and data access of the application.

Here’s an example of how you might implement some simple repositories and controllers for a user and post model in Python using Flask:

# models/user.py
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email

Now, for the example code structure mentioned above, here is some simple example code for each of the files:

app/init.py

This is some sample code for the init file in the app directory:

# app/__init__.py
from flask import Flask
from app.models import db
def create_app():
app = Flask(__name__)
app.config.from_object('app.config.Config')
db.init_app(app)
with app.app_context():
db.create_all()
from app.views import user_view, post_view
app.register_blueprint(user_view.bp)
app.register_blueprint(post_view.bp)
return app
  • The code creates a Flask application object using the Flask class from the flask module.
  • The configuration for the application is set using the app.config.from_object method, which loads the Config class from the app.config module.
  • The db object from the app.models module is initialized with the application object using the db.init_app(app) method.
  • A database is created using the db.create_all() method within an application context using with app.app_context():. This context is necessary for the db object to work properly.
  • Two views are registered using app.register_blueprint() method. These views are located in the app.views module, and are named user_view and post_view.
  • The application object is returned from the create_app() function, which can then be used to run the Flask application.

app/config.py

The config file in the app directory has all the secret tokens.

# app/config.py
class Config:
SQLALCHEMY_DATABASE_URI = 'sqlite:///mydatabase.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = 'mysecretkey'
  • The Config class is defined with three class attributes: SQLALCHEMY_DATABASE_URI, SQLALCHEMY_TRACK_MODIFICATIONS, and SECRET_KEY.
  • SQLALCHEMY_DATABASE_URI is a string that specifies the URI for the database that the Flask application will use. In this case, it is set to a SQLite database named mydatabase.db.
  • SQLALCHEMY_TRACK_MODIFICATIONS is a boolean value that indicates whether or not the Flask-SQLAlchemy extension should track modifications to objects and emit signals when changes are made. In this case, it is set to False to disable this feature.
  • SECRET_KEY is a string that is used to encrypt and secure data in the application. It should be a long and random string that is kept secret. In this case, it is set to mysecretkey.

app/models/__init__.py

The init file in the models directory has code to SQLAlchemy initialization.

# app/models/__init__.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
  • The flask_sqlalchemy module is imported.
  • The SQLAlchemy class from the flask_sqlalchemy module is instantiated and assigned to the db variable.
  • The db object can be used to interact with a database in a Flask application.
  • The db object is typically used to define models (i.e., database tables) using SQLAlchemy's ORM (Object-Relational Mapping) functionality.
  • The db object is also used to execute queries against the database, such as inserting, updating, and retrieving records.
  • By initializing the db object in this way, it becomes available to other modules within the Flask application, allowing them to use the same instance of the db object to interact with the database. This helps to ensure consistency and avoid conflicts when accessing the database from different parts of the application.

app/models/user.py

Here is some sample code for the User model, which includes the database columns:

# app/models/user.py
from datetime import datetime
from app.models import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
def __repr__(self):
return '<User %r>' % self.username
  • The datetime module is imported to enable working with timestamps.
  • The db object is imported from the app.models module. This object is an instance of the SQLAlchemy class, which is used to define the model.
  • The User class is defined with four attributes: id, username, email, and created_at.
  • The id attribute is defined as an integer column with the primary_key attribute set to True, indicating that it is the primary key for the table.
  • The username attribute is defined as a string column with a maximum length of 80 characters, and the unique and nullable attributes set to True and False, respectively. This means that the column must have a unique value and cannot be null.
  • The email attribute is defined similarly to the username attribute, but with a maximum length of 120 characters.
  • The created_at attribute is defined as a DateTime column with the nullable attribute set to False, indicating that it cannot be null, and the default attribute set to datetime.utcnow, which sets the default value to the current UTC time.
  • The __repr__ method is defined to provide a string representation of the User object. In this case, it returns a string containing the username attribute.

This User model can now be used to interact with the database using SQLAlchemy's ORM functionality. For example, to create a new user in the database, you could do something like this:

new_user = User(username='johndoe', email='johndoe@example.com')
db.session.add(new_user)
db.session.commit()

This would create a new User object with the specified username and email attributes and add it to the database using the db.session.add() method. The changes are then committed to the database using the db.session.commit() method.

Why are Repository layers required?

Dependancy injections.

app/models/repositories/user_repository.py

This is the user repository, containing all the functions which interact with the database:

# app/models/repositories/user_repository.py
from app.models import db
from app.models.user import User
class UserRepository:
def get_all(self):
return User.query.all()
def get_by_id(self, id):
return User.query.get(id)
def create(self, username, email):
user = User(username=username, email=email)
db.session.add(user)
db.session.commit()
return user
  • The db object is imported from the app.models module, as well as the User model from the app.models.user module.
  • The UserRepository class is defined with three methods: get_all(), get_by_id(), and create().
  • The get_all() method returns a list of all User objects in the database using the User.query.all() method, which generates a SELECT statement to retrieve all rows from the users table.
  • The get_by_id() method takes an id parameter and returns the User object with the corresponding id using the User.query.get(id) method, which generates a SELECT statement with a WHERE clause to retrieve the row with the specified id.
  • The create() method takes username and email parameters, creates a new User object with those attributes, adds the object to the database session using the db.session.add() method, and commits the changes to the database using the db.session.commit() method. Finally, the method returns the newly created User object.

The UserRepository class provides an abstraction layer for accessing the User model in the database, which can be useful for encapsulating database access logic and promoting code modularity. For example, instead of writing SQL queries directly in the application code, you can use the UserRepository methods to perform common database operations on the User model.

app/models/post.py

This is the post database model which defines a Post model that can be used to store and retrieve blog posts in the database.

from datetime import datetime 
from app.models import db
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
author_id = db.Column(db.Integer, db.ForeignKey(‘user.id’), nullable=False)
author = db.relationship(‘User’, backref=db.backref(‘posts’, lazy=True))

def repr(self):
return ‘<Post %r>’ % self.title
  • The datetime module is imported to provide the current timestamp when creating new Post objects.
  • The db object is imported from the app.models module.
  • The Post class is defined, which is a subclass of db.Model. This means that the Post class inherits from SQLAlchemy's Model class and is a SQLAlchemy model that can be used to interact with the database.
  • The Post class has several attributes defined using SQLAlchemy's column types, including id, title, content, created_at, author_id, and author.
  • id is an integer primary key column, which uniquely identifies each Post object in the database.
  • title is a string column that stores the title of the blog post.
  • content is a text column that stores the body of the blog post.
  • created_at is a datetime column that stores the date and time when the post was created. It is set to the current time by default when a new Post object is created.
  • author_id is an integer foreign key column that references the id column of the User model. This establishes a one-to-many relationship between User and Post, where each Post object is associated with one User.
  • author is a relationship that defines the User object associated with each Post object. The backref parameter specifies that the User object should have a posts attribute that contains a list of all posts created by that user. The lazy parameter specifies that the related Post objects should be loaded lazily, meaning that they will only be fetched from the database when accessed.
  • The repr() method is defined to provide a string representation of a Post object. It returns a string that includes the title of the post.

app/models/repositories/post_repository.py

The post repository contains methods which interact with the session:

from app.models import db
from app.models.post import Post
class PostRepository:
def get_all(self):
return Post.query.all()
def get_by_id(self, id):
return Post.query.get(id)
def create(self, title, content, author_id):
post = Post(title=title, content=content, author_id=author_id)
db.session.add(post)
db.session.commit()
return post
  • The db object and Post class are imported from the app.models module.
  • The PostRepository class is defined, which has three methods: get_all(), get_by_id(), and create().
  • get_all() returns a list of all Post objects in the database, using the query.all() method provided by SQLAlchemy's ORM.
  • get_by_id() takes an id parameter and returns the Post object with that id, using the query.get() method provided by SQLAlchemy's ORM.
  • create() takes title, content, and author_id parameters, creates a new Post object with those values, and adds it to the database using the db.session.add() method. The changes are then committed to the database using the db.session.commit() method. Finally, the new Post object is returned.

These methods provide a convenient interface for interacting with Post objects in the database, without having to write raw SQL queries. By using the PostRepository class, other parts of the Flask application can easily create, read, update, and delete Post objects as needed.

app/controllers/user_controller.py

The controller layer contains business logic. This is the code for user_controller.py :

from flask import Blueprint, request, jsonify
from app.models.repositories.user_repository import UserRepository

bp = Blueprint(‘user’, name, url_prefix=’/user’)
userRepository = UserRepository()

@bp.route(‘/’)
def get_users():
users = userRepository.get_all()
return jsonify([{‘id’: user.id, ‘username’: user.username, ‘email’: user.email} for user in users])

@bp.route(‘/’, methods=[‘POST’])
def create_user():
data = request.get_json()
user = userRepository.create(data[‘username’], data[‘email’])
return jsonify({‘id’: user.id, ‘username’: user.username, ‘email’: user.email})
  • A Blueprint object is created with the name 'user' and the URL prefix '/user'.
  • An instance of UserRepository is created.
  • A route is defined for handling GET requests to the ‘/user’ URL. When a GET request is received, the get_all() method of the UserRepository object is called to retrieve a list of all users from the database. The list of users is then returned as a JSON response.
  • Another route is defined for handling POST requests to the ‘/user’ URL. When a POST request is received, the JSON data sent in the request body is retrieved using request.get_json(). The create() method of the UserRepository object is then called to create a new user in the database with the data provided. The newly created user is returned as a JSON response.
  • The bp object and the two routes are returned from the module, so that they can be registered with the Flask application in another module.

By defining routes for handling GET and POST requests to the ‘/user’ URL, this blueprint provides a simple API for retrieving and creating user objects in the application. The UserRepository class is used to interact with the database, so the controller module is not directly responsible for managing the database connection.

app/controllers/post_controller.py

from flask import Blueprint, request, jsonify
from app.models.repositories.post_repository import PostRepository

bp = Blueprint(‘post’, name, url_prefix=’/post’)
postRepository = PostRepository()

@bp.route(‘/’)
def get_posts():
posts = postRepository.get_all()
return jsonify([{‘id’: post.id, ‘title’: post.title, ‘content’: post.content, ‘author_id’: post.author_id} for post in posts])

@bp.route(‘/’, methods=[‘POST’]) def create_post(): data = request.get_json()
post = postRepository.create(data[‘title’], data[‘content’], data[‘author_id’])
return jsonify({‘id’: post.id, ‘title’: post.title, ‘content’: post.content, ‘author_id’: post.author_id})

The code defines a blueprint named bp for handling POST and GET requests related to posts. It imports the PostRepository class from the app.models.repositories.post_repository module to handle database operations related to posts.

The blueprint has two routes:

  1. /post/: This route handles GET requests to retrieve all posts in the database. It calls the get_all() method of the PostRepository class to fetch all posts and returns a JSON response with the data.
  2. /post/: This route handles POST requests to create a new post in the database. It first retrieves the JSON data from the request using request.get_json(), then calls the create() method of the PostRepository class to create a new post with the given data. It then returns a JSON response with the newly created post's information, including the ID, title, content, and author ID.

app/views/user_view.py

The views folder contains the different views, i.e. the post view and the user view :

from flask import Blueprint, jsonify
from app.controllers.user_controller import UserController

user_controller = UserController()
user_view = Blueprint('user_view', __name__, url_prefix='/user')

@user_view.route('/')
def get_users():
users = user_controller.get_users()
return jsonify(users)

@user_view.route('/', methods=['POST'])
def create_user():
data = request.get_json()
user = user_controller.create_user(data)
return jsonify(user)

@user_view.route('/<int:user_id>')
def get_user(user_id):
user = user_controller.get_user(user_id)
if user:
return jsonify(user)
else:
return '', 404
  • from flask import Blueprint, jsonify: Importing the Blueprint and jsonify modules from Flask.
  • from app.controllers.user_controller import UserController: Importing the UserController class from app.controllers.user_controller.
  • user_controller = UserController(): Creating an instance of the UserController class.
  • user_view = Blueprint('user_view', __name__, url_prefix='/user'): Creating a Blueprint object named user_view with the name of 'user_view', the current module name as the second argument (__name__), and a URL prefix of '/user'.
  • @user_view.route('/'): Decorating the get_users function below it with a route that handles HTTP GET requests to the URL '/user/'.
  • def get_users():: Defining a function that retrieves all users and returns them in JSON format.
  • users = user_controller.get_users(): Calling the get_users method of the UserController instance created earlier to retrieve all users from the database.
  • return jsonify(users): Converting the users list to a JSON string using Flask's jsonify function and returning it as an HTTP response.
  • @user_view.route('/', methods=['POST']): Decorating the create_user function below it with a route that handles HTTP POST requests to the URL '/user/'.
  • def create_user():: Defining a function that creates a new user with the data provided in the request and returns the new user in JSON format.
  • data = request.get_json(): Retrieving the JSON data from the request.
  • user = user_controller.create_user(data): Calling the create_user method of the UserController instance created earlier to create a new user in the database.
  • return jsonify(user): Converting the new user object to a JSON string using Flask's jsonify function and returning it as an HTTP response.
  • @user_view.route('/<int:user_id>'): Decorating the get_user function below it with a route that handles HTTP GET requests to the URL '/user/<user_id>', where user_id is an integer.
  • def get_user(user_id):: Defining a function that retrieves a single user with the given user_id and returns it in JSON format.
  • user = user_controller.get_user(user_id): Calling the get_user method of the UserController instance created earlier to retrieve a single user from the database.
  • if user:: Checking if a user was found with the given user_id.
  • return jsonify(user): Converting the user object to a JSON string using Flask's jsonify function and returning it as an HTTP response.
  • else:: If no user was found with the given user_id, returning an empty string with a 404 status code to indicate a "Not Found" error.

app/views/post_view.py

from flask import Blueprint, request, jsonify
from app.controllers.post_controller import PostController

bp = Blueprint('post_view', __name__, url_prefix='/post')
postController = PostController()

@bp.route('/')
def get_posts():
posts = postController.get_all_posts()
return jsonify([{'id': post.id, 'title': post.title, 'content': post.content, 'author_id': post.author_id} for post in posts])

@bp.route('/', methods=['POST'])
def create_post():
data = request.get_json()
post = postController.create_post(data['title'], data['content'], data['author_id'])
return jsonify({'id': post.id, 'title': post.title, 'content': post.content, 'author_id': post.author_id})

This code creates a Flask Blueprint for handling requests related to posts, and connects it to a PostController object that handles the business logic for posts.

The Blueprint object is created with the name "post_view" and the URL prefix "/post". The PostController object is also instantiated.

There are two routes defined for this blueprint:

  1. A GET route at the URL "/" that retrieves all posts by calling postController.get_all_posts(). The retrieved posts are then formatted as JSON and returned to the client.
  2. A POST route at the URL "/" that creates a new post by calling postController.create_post(). The data for the new post is obtained from the request body as JSON, and the new post is created with the provided information. The newly created post is then formatted as JSON and returned to the client.

templates/base.html

<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8">
<title>{% block title %}My App{% endblock %}</title>
<link rel=”stylesheet” href=”{{ url_for(‘static’, filename=’css/main.css’) }}”>
</head>
<body>
<nav>
<ul>
<li><a href=”{{ url_for(‘user.get_users’) }}”>Users</a></li>
<li><a href=”{{ url_for(‘post.get_posts’) }}”>Posts</a></li>
</ul>
</nav>
<main>
{% block content %}
{% endblock %}
</main>
<script src=”{{ url_for(‘static’, filename=’js/main.js’) }}”></script>
</body>
</html>
  • This is an HTML file with the lang attribute set to "en".
  • The head section contains a meta tag specifying the character encoding and a title tag that can be overridden by child templates using the block syntax.
  • The link tag loads a CSS file located in the static/css directory.
  • The body section contains a nav element with an unordered list of navigation links, including links to the "Users" and "Posts" pages generated using Flask's url_for function.
  • The main element contains the main content of the page, which can be overridden by child templates using the block syntax.
  • The script tag loads a JavaScript file located in the static/js directory.

templates/user.html

{% extends 'base.html' %}

{% block content %}
<h1>Users</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<form id="create-user-form">
<label for="username">Username:</label>
<input type="text" id="username" name="username"><br>
<label for="email">Email:</label>
<input type="text" id="email" name="email"><br>
<button type="submit">Create User</button>
</form>
{% endblock %}

{% block scripts %}
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
<script>
// Handle form submission to create new user
const form = document.getElementById('create-user-form');
form.addEventListener('submit', (event) => {
event.preventDefault();
const username = form.elements.username.value;
const email = form.elements.email.value;
createUser(username, email);
form.reset();
});
</script>
{% endblock %}

templates/post.html

  • The {% block content %} and {% endblock %} tags define the content of the page, which will be placed inside the {% block content %} tag in the base.html file.
  • The page displays a table with three columns (ID, Username, Email) and rows of user data.
  • The {% for user in users %} and {% endfor %} tags define a loop that iterates over the list of users passed to the template from the backend.
  • The <form> element at the bottom of the page allows the user to create a new user by entering a username and email address.
  • The {% block scripts %} and {% endblock %} tags define a section for scripts to be included at the bottom of the page.
  • The script in this section listens for a form submission event and extracts the values entered by the user, which are then passed to a JavaScript function createUser() defined in the main.js file included in the base.html file.
{% extends 'base.html' %}

{% block title %}{{ post.title }}{% endblock %}

{% block content %}
<article>
<header>
<h1>{{ post.title }}</h1>
<p>By {{ post.author.username }}</p>
</header>
<div>
{{ post.content }}
</div>
</article>
{% endblock %}

static/css/main.css

/* Global styles */

body {
font-family: sans-serif;
line-height: 1.5;
margin: 0;
padding: 0;
}

header {
background-color: #333;
color: #fff;
padding: 1rem;
}

nav {
display: flex;
justify-content: space-between;
}

nav a {
color: #fff;
text-decoration: none;
}

nav a:hover {
text-decoration: underline;
}

main {
margin: 2rem;
}

/* Page-specific styles */

/* User view */

.user {
margin-bottom: 1rem;
padding: 1rem;
border: 1px solid #ccc;
}

.user h2 {
margin: 0 0 1rem;
}

.user p {
margin: 0;
}

/* Post view */

article {
margin-bottom: 2rem;
}

article h1 {
margin: 0 0 1rem;
}

article p {
margin: 0 0 1rem;
}

article div {
font-size: 1.2rem;
line-height: 1.5;
}

The main.css file is a Cascading Style Sheet (CSS) file that contains styles to define the look and feel of the HTML elements in the web application. Here's a breakdown of the code:

/* Global styles */

  • body: sets the font family, line height, margin, and padding for the entire web page.
  • header: sets the background color, text color, and padding for the header element.
  • nav: sets the display to flex and justifies the content between the navigation links.
  • nav a: sets the color and removes text decoration for the navigation links.
  • nav a:hover: sets text decoration to underline when hovering over the navigation links.
  • main: sets the margin around the main content area.

/* Page-specific styles */

  • .user: sets the margin, padding, and border of the user element.
  • .user h2: sets the margin of the user header element.
  • .user p: sets the margin of the user paragraph element.
  • article: sets the margin of the article element.
  • article h1: sets the margin of the article header element.
  • article p: sets the margin of the article paragraph element.
  • article div: sets the font size and line height of the article div element.

static/js/main.js

// get the form and input fields
const postForm = document.querySelector('#post-form');
const titleInput = document.querySelector('#title');
const contentInput = document.querySelector('#content');

// add event listener for form submission
postForm.addEventListener('submit', (event) => {
// prevent the default form submission behavior
event.preventDefault();

// get the input values
const title = titleInput.value.trim();
const content = contentInput.value.trim();

// make a POST request to create a new post
fetch('/post', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: title,
content: content,
author_id: 1 // replace with the actual author ID
})
})
.then(response => response.json())
.then(data => {
// clear the form inputs
titleInput.value = '';
contentInput.value = '';

// display a success message
const successMsg = document.querySelector('#success-msg');
successMsg.innerHTML = 'Post created successfully!';
successMsg.classList.remove('hidden');

// add the new post to the page
const postList = document.querySelector('#post-list');
const postHTML = `
<div class="card">
<div class="card-header">
<h2>${data.title}</h2>
</div>
<div class="card-body">
<p>${data.content}</p>
<p>Author: ${data.author_id}</p>
</div>
</div>
`;
postList.innerHTML += postHTML;
})
.catch(error => {
console.error(error);
// display an error message
const errorMsg = document.querySelector('#error-msg');
errorMsg.innerHTML = 'An error occurred while creating the post.';
errorMsg.classList.remove('hidden');
});
});

This code is a JavaScript file called main.js which is used in a web application. Here is an explanation of the code:

  1. const postForm = document.querySelector('#post-form');: This line selects the HTML form with the id of post-form and assigns it to a constant called postForm.
  2. const titleInput = document.querySelector('#title');: This line selects the HTML input field with the id of title and assigns it to a constant called titleInput.
  3. const contentInput = document.querySelector('#content');: This line selects the HTML input field with the id of content and assigns it to a constant called contentInput.
  4. postForm.addEventListener('submit', (event) => { ... });: This line adds an event listener to the postForm element that listens for a submit event. When the form is submitted, the callback function is called.
  5. event.preventDefault();: This line prevents the default form submission behavior, which would cause the page to reload.
  6. const title = titleInput.value.trim();: This line gets the value of the titleInput field and trims any leading or trailing whitespace. The result is assigned to a constant called title.
  7. const content = contentInput.value.trim();: This line gets the value of the contentInput field and trims any leading or trailing whitespace. The result is assigned to a constant called content.
  8. fetch('/post', { ... });: This line makes a POST request to the /post endpoint of the web application, sending a JSON payload that includes the title, content, and author_id fields.
  9. .then(response => response.json()): This line extracts the JSON data from the response object.
  10. .then(data => { ... });: This line defines a callback function that is called when the data has been successfully extracted from the response object. It clears the form inputs, displays a success message, and adds the new post to the page.
  11. .catch(error => { ... });: This line defines a callback function that is called if an error occurs during the fetch() request. It logs the error to the console and displays an error message.

Tests

Inside the tests folder, it is great to have different test folders for the views, controllers and the data model. This is an example structure of how the files and folders should be structured.

tests\
init.py
views\
test_user_view.py
test_post_view.py
controller\
test_user_controller.py
test_post_controller.py
models\
test_user.py\
test_post.py\
repository\
test_user_repository.py\
test_post_repository.py\

Here are a few examples of the code for the repository tests:

tests/init.py

# empty file

tests/test_user.py

  • The test_user.py file contains two unit test functions for the UserRepository class.
  • The first function test_get_all_users() tests the get_all() method of the UserRepository class by creating a new instance of the class and calling the get_all() method to retrieve all users. The test then checks if the length of the users list is equal to zero.
  • The second function test_create_user() tests the create() method of the UserRepository class by creating a new instance of the class and calling the create() method to create a new user with the username 'testuser' and email 'testuser@example.com’. The test then checks if the username and email of the created user object match the expected values 'testuser' and 'testuser@example.com’.

These unit tests ensure that the UserRepository class is working correctly and that it can retrieve all users and create new users with the expected values.

from app.models.repositories.user_repository import UserRepository
def test_get_all_users():
repo = UserRepository()
users = repo.get_all()
assert len(users) == 0
def test_create_user():
repo = UserRepository()
user = repo.create(‘testuser’, ‘testuser@example.com’)
assert user.username == ‘testuser’
assert user.email == ‘testuser@example.com’

tests/test_post.py

from app.models.repositories.post_repository import PostRepository
def test_get_all_posts():
repo = PostRepository()
posts = repo.get_all()
assert len(posts) == 0

def test_create_post():
repo = PostRepository()
post = repo.create(‘Test Post’, ‘This is a test post.’)
assert post.title == ‘Test Post’
assert post.body == ‘This is a test post.’

The test_post.py file contains two test functions for testing the functionality of the PostRepository class in the app.models.repositories.post_repository module.

  • The first test function test_get_all_posts checks whether the get_all() method of PostRepository returns an empty list when no posts have been created yet. It does this by creating an instance of the PostRepository class, calling the get_all() method to retrieve all posts, and then checking whether the length of the resulting list is 0. If the test passes, it means that the get_all() method is working correctly and returning an empty list when there are no posts in the database.
  • The second test function test_create_post checks whether the create() method of PostRepository correctly creates a new post with the given title and body. It does this by creating an instance of the PostRepository class, calling the create() method with the title and body of the test post, and then checking whether the resulting post object has the correct title and body values. If the test passes, it means that the create() method is working correctly and creating a new post with the specified title and body.

docs/index.md

This is the main documentation page.

docs/user.md

This page documents the user-related functionality.

docs/post.md

This page documents the post-related functionality.

Conclusion

Writing structured code is essential for creating programs that are easy to understand, maintain, and extend. Structured code allows developers to write clean, readable, and efficient code that is less prone to errors and bugs. By using meaningful variable and function names, commenting code, and breaking code into logical units, developers can make it easier for themselves and others to understand and modify the code as needed. Structured code also helps to promote code reuse and makes it easier to test and debug. Writing structured code is an important skill for any programmer to master, and it can lead to more robust and reliable software applications.

That’s it for this article! Feel free to leave feedback or questions in the comments. If you found this an exciting read, leave some claps and follow! I love coffee, so feel free to buy me a coffee at https://bmc.link/pycodenemesis XD. Cheers!

--

--