SA-02: Phase 3 Code Challenge: Articles - without SQLAlchemy [COMPULSORY]
- Due May 27 by 11:59pm
- Points 12
- Submitting a website url
- Available after May 22 at 12am
Object Relations Code Challenge - Articles
Problem Statement
Build a system to model the relationship between Authors, Articles, and Magazines, with data persisted in a SQL database. In this domain:
- An `Author` can write many `Articles`
- A `Magazine` can publish many `Articles`
- An `Article` belongs to both an `Author` and a `Magazine`
- The `Author` - `Magazine` relationship is many-to-many
Setup Instructions
Choose one of the following options to set up your environment:
## Option 1: Using Pipenv
1. Install dependencies
pipenv install pytest sqlite3
2. Activate the virtual environment
pipenv shell
## Option 2: Using venv
1. Create a virtual environment
python -m venv env
2. Activate virtual environment (Mac/Linux)
source env/bin/activate
# OR (Windows)
# env\Scripts\activate
3. Install dependencies
pip install pytest
4. Database Setup
You can choose between SQLite (simpler) or PostgreSQL (more powerful) for this challenge:
### Option 1: SQLite (Recommended for beginners)
In lib/db/connection.py
import sqlite3
def get_connection():
conn = sqlite3.connect('articles.db')
conn.row_factory = sqlite3.Row # This enables column access by name
return conn
### Option 2: PostgreSQL
1. First create a database in PostgreSQL
createdb articles_challenge
# In lib/db/connection.py
import psycopg2
from psycopg2.extras import RealDictCursor
def get_connection():
conn = psycopg2.connect(
"dbname=articles_challenge user=your_username password=your_password"
)
conn.cursor_factory = RealDictCursor # This enables column access by name
return conn
```
## Recommended Project Structure
Use this structure with database components:
```
code-challenge/
├── lib/ # Main code directory
│ ├── models/ # Model classes
│ │ ├── __init__.py # Makes models a package
│ │ ├── author.py # Author class with SQL methods
│ │ ├── article.py # Article class with SQL methods
│ │ └── magazine.py # Magazine class with SQL methods
│ ├── db/ # Database components
│ │ ├── __init__.py # Makes db a package
│ │ ├── connection.py # Database connection setup
│ │ ├── seed.py # Seed data for testing
│ │ └── schema.sql # SQL schema definitions
│ ├── controllers/ # Optional: Business logic
│ │ └── __init__.py # Makes controllers a package
│ ├── debug.py # Interactive debugging
│ └── __init__.py # Makes lib a package
├── tests/ # Test directory
│ ├── __init__.py # Makes tests a package
│ ├── test_author.py # Tests for Author class
│ ├── test_article.py # Tests for Article class
│ └── test_magazine.py # Tests for Magazine class
├── scripts/ # Helper scripts
│ ├── setup_db.py # Script to set up the database
│ └── run_queries.py # Script to run example queries
└── README.md # Project documentation
```
### Structure Guidelines:
1. **Models**: Python classes that interact with the database via SQL
- `author.py`: Author class with methods using SQL queries
- `article.py`: Article class with relationships to Author and Magazine
- `magazine.py`: Magazine class with relationships
2. **Database Layer**:
- `connection.py`: Database connection handling
- `schema.sql`: Table definitions and constraints
- `seed.py`: Populate database with test data
3. **Package Organization**:
- Use `__init__.py` files to make directories into packages
- Each model file should handle its own SQL queries
4. **Testing**:
- Create separate test files for each model
- Tests should verify SQL queries and data integrity
- Run tests with `pytest` from the main directory
## Testing Your Code
- Run `pytest` to verify your implementation
- For debugging, use `python lib/debug.py` to start an interactive session
- Set up test database with: `python scripts/setup_db.py`
## Deliverables
1. Database Schema
Create SQL tables for Authors, Articles, and Magazines with appropriate relationships:
` ``sql
-- Example schema (implement in lib/db/schema.sql)
CREATE TABLE IF NOT EXISTS authors (
id INTEGER PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS magazines (
id INTEGER PRIMARY KEY,
name VARCHAR(255) NOT NULL,
category VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY,
title VARCHAR(255) NOT NULL,
author_id INTEGER,
magazine_id INTEGER,
FOREIGN KEY (author_id) REFERENCES authors(id),
FOREIGN KEY (magazine_id) REFERENCES magazines(id)
);
```
2. Python Classes with SQL Methods
#### Author Class
- Implement an Author class with proper initialization
- Write SQL methods to:
- Save an author to the database
- Find an author by ID or name
- Properties and validations for name
- Include methods to work with relationships
#### Magazine Class
- Implement a Magazine class with proper initialization
- Write SQL methods to:
- Save a magazine to the database
- Find a magazine by ID, name, or category
- Properties and validations for name and category
- Include methods to work with relationships
#### Article Class
- Implement an Article class with proper initialization
- Write SQL methods to:
- Save an article to the database
- Find articles by ID, title, author, or magazine
- Properties and validations for title
- Include methods to work with relationships
3. SQL Query Methods
Implement these SQL queries within your model classes:
1. Get all articles written by a specific author
``` python
# Example in Author class
def articles(self):
conn = get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT * FROM articles
WHERE author_id =?
""", (self.id,))
return cursor.fetchall()
```
2. Find all magazines a specific author has contributed to
``` python
# Example in Author class
def magazines(self):
conn = get_connection()
cursor = conn.cursor()
cursor.execute("""
SELECT DISTINCT m.* FROM magazines m
JOIN articles a ON m.id = a.magazine_id
WHERE a.author_id =?
""", (self.id,))
return cursor.fetchall()
```
-
Get all authors who have written for a specific magazine
-
Find magazines with articles by at least 2 different authors
-
Count the number of articles in each magazine
-
Find the author who has written the most articles
4. Relationship Methods
#### Author Methods
- `articles()`: Returns list of all articles written by the author (using SQL)
- `magazines()`: Returns unique list of magazines the author has contributed to (using SQL)
- `add_article(magazine, title)`: Creates and inserts a new Article into the database
- `topic_areas()`: Returns unique list of categories of magazines the author has contributed to (using SQL)
#### Magazine Methods
- `articles()`: Returns list of all articles published in the magazine (using SQL)
- `contributors()`: Returns unique list of authors who have written for this magazine (using SQL)
- `article_titles()`: Returns list of titles of all articles in the magazine (using SQL)
- `contributing_authors()`: Returns list of authors with more than 2 articles in the magazine (using SQL)
5. Database Transactions
Implement transaction handling with Python's context managers:
```python
# Example transaction handling
def add_author_with_articles(author_name, articles_data):
"""
Add an author and their articles in a single transaction
articles_data: list of dicts with 'title' and 'magazine_id' keys
"""
conn = get_connection()
try:
conn.execute("BEGIN TRANSACTION")
cursor = conn.cursor()
# Insert author
cursor.execute(
"INSERT INTO authors (name) VALUES (?) RETURNING id",
(author_name,)
)
author_id = cursor.fetchone()[0]
# Insert articles
for article in articles_data:
cursor.execute(
"INSERT INTO articles (title, author_id, magazine_id) VALUES (?,?,?)",
(article['title'], author_id, article['magazine_id'])
)
conn.execute("COMMIT")
return True
except Exception as e:
conn.execute("ROLLBACK")
print(f"Transaction failed: {e}")
return False
finally:
conn.close()
```
### Bonus Challenges
-
Implement `Magazine.top_publisher()` class method using a complex SQL query to find the magazine with the most articles
-
Add database indexes to improve query performance
-
Implement a CLI tool that allows users to query the database interactively
## Evaluation Criteria
- Working code that correctly uses raw SQL queries within Python classes
- Proper database schema design with correct relationships
- SQL queries that are efficient and correct
- Transaction management and error handling
- Code organization and adherence to OOP principles
- Protection against SQL injection
- Test coverage of all SQL operations
## Version Control Requirements
You must create a Git repository and commit your work incrementally. Follow these guidelines:
### Repository Setup
-
Create a new Git repository for this challenge
-
Initialize with a README.md explaining the project
-
Set up a `.gitignore` file for Python (include virtual environments, cache files, database files, etc.)
### Commit Practices
-
Make small, focused commits that represent logical units of work
-
Commit frequently as you complete features or fix issues
-
Write clear, concise commit messages following this format:
```
[Feature/Fix/Refactor]: Brief description of what changed
- More detailed explanation if needed
- List specific changes made
```
### Commit Message Examples
- "Initialize project structure and database connection"
- "Implement Author class with SQL methods"
- "Add queries for article listing by author"
- "Fix transaction handling in article creation"
- "Optimize query performance for magazine contributors"
### Recommended Commit Sequence
-
Initial project setup and database connection
-
Database schema creation
-
Basic class implementation with SQL methods
-
Relationship query methods
-
More complex SQL query implementations
-
Transaction handling and error management
-
Tests and optimization
-
Documentation updates
### Submission
When you're ready to submit, ensure:
-
All tests pass
-
Your database schema is properly implemented
-
SQL queries are properly implemented and optimized
-
Your code is properly documented
-
Your repository shows a clear progression of work through commits
-
You've addressed all the requirements in the deliverables
Keep in mind that 34 students have already been assessed using this rubric. Changing it will affect their evaluations.
Phase 3 Code Challenge Rubric
| Criteria | Ratings | Pts |
|---|---|---|
| Build classes using Python OOP Edit criterion description | 4 pts 4 Correct class syntax for all of the submitted code. Code express intent: method and variable names indicate their behavior and data types. Follows DRY principles and reuses code where applicable. 3 pts 3 Correct class syntax for all of the submitted code. Code express intent: method and variable names indicate their behavior and data types. Some deliverables might not be complete. No methods are duplicated. 2 pts 2 May be incomplete or missing deliverables. Most code generally expresses intent: method and variable names match their behaviors and data types. Methods are not duplicated. Code does not have unused or methods without a clear purpose. 1 pts 1 No syntax errors. Some deliverables function correctly. Mixes up syntax for class and instance methods, refers to variables in the wrong scope, or uses self incorrectly, leading to logical errors. Method and variable names do not correspond to their respective behavior or data types. Methods might be duplicated, or code includes unused methods without a clear purpose. 0 pts 0 Class code has a syntax or runtime error. Code does not run at all, or exits with an error, or most Object-oriented deliverables are not implemented. | pts 4 pts -- |
| Object relationships Edit criterion description | 4 pts 4 Relationships properly created. Generates relationships programmatically when called for. Uses properties and attribute accessors to validate relationships. 3 pts 3 Relationships properly created. Generates relationships programmatically when called for. Attempts to use properties and attribute accessors to validate relationships. 2 pts 2 Relationships attempted have used some mistakes. Properties and attribute accessors do not properly validate relationships. 1 pts 1 Relationships attempted in the domain but are incorrect. Attempts do not have properties and attribute accessors to validate relationships. 0 pts 0 No object relationships present. | pts 4 pts -- |
| Aggregate and association methods Edit criterion description | 4 pts 4 All aggregate and association methods are implemented and function correctly. Methods are solved elegantly, with little to no unneeded code. 3 pts 3 All aggregate and association methods are implemented and function correctly. Some code is unused or unnecessary. 2 pts 2 Methods do not work correctly, or might be unimplemented. Code may not use the appropriate built in method for each problem, or duplicate logic instead of using helper methods. 1 pts 1 Aggregate and association methods have runtime or logic errors. Methods may use the wrong iterators, have incorrect logic; or many of the methods are unimplemented. 0 pts 0 Does not attempt to solve aggregate and association method problems, or has syntax errors code. | pts 4 pts -- |