Async Python library for the 4writers.net API
Project description
py4writers
Async Python client for the 4writers.net platform API. Fetch orders, download files, accept work — all from your own scripts.
Features
- Fully async — built on
aiohttp, designed forasync with/await - Streaming iterators —
iter_orders(),iter_completed_orders(), etc. yield one order at a time without loading everything into memory - Automatic retry — exponential backoff on network errors (configurable)
- Rate limiting — semaphore-based concurrency control (default 10 parallel requests)
- Typed models —
OrderandFiledataclasses with full type hints - Clean error hierarchy —
AuthenticationError,NetworkError,ParsingError, etc.
Installation
pip install py4writers
Or with Poetry:
poetry add py4writers
Quick start
import asyncio
from py4writers import API
async def main():
async with API(login="your_login", password="your_password") as api:
await api.login()
orders = await api.get_orders()
for order in orders:
print(f"{order.order_id}: {order.title} — ${order.total:.2f}")
asyncio.run(main())
Usage
Fetching available orders
Batch — loads all orders from one page at once, with descriptions and files:
orders = await api.get_orders(page=1, page_size=50, category="essay")
for order in orders:
print(order.title, order.total, order.description)
Streaming — yields orders one-by-one, automatically paginates:
async for order in api.iter_orders(max_pages=3):
print(order.title, order.total)
if order.total > 50:
break # stop early whenever you want
Completed orders
completed = await api.get_completed_orders()
for order in completed:
print(f"{order.title} — paid ${order.your_payment:.2f}")
# or streaming:
async for order in api.iter_completed_orders():
print(order.title, order.editor_work, order.your_payment)
Active orders (in progress / revision / late)
# Generic:
orders = await api.get_active_orders(order_type="processing")
# Convenience wrappers:
processing = await api.get_processing_orders()
revisions = await api.get_revision_orders()
late = await api.get_late_orders()
# Streaming:
async for order in api.iter_processing_orders():
print(order.title, order.remaining)
Files
# List files attached to an order:
files = await api.get_order_files(order_index=2569038)
for f in files:
print(f.name, f.author, f.date)
# Download a file:
content = await api.download_file(file_id=f.id)
with open(f.name, "wb") as fp:
fp.write(content)
# Stream files one by one:
async for f in api.iter_order_files(order_index=2569038):
data = await api.download_file(f.id)
save(f.name, data)
Order details
description = await api.fetch_order_details(order_index=2569038)
print(description)
# For completed orders:
description = await api.fetch_order_details(order_index=2569038, is_completed=True)
Taking an order
success = await api.take_order(order_index=2569038)
if success:
print("Order accepted!")
Data models
Order
| Field | Type | Description |
|---|---|---|
title |
str |
Order title |
subject |
str |
Subject area |
order_id |
str |
Public order ID |
order_index |
int |
Internal index (used in API calls) |
deadline |
str |
Deadline date/time |
remaining |
str |
Time remaining |
order_type |
str |
Essay, Research Paper, etc. |
academic_level |
str |
College, Bachelor, Master, etc. |
style |
str |
APA, MLA, Chicago, etc. |
language |
str |
Language |
pages |
int |
Page count |
sources |
int |
Required sources |
salary |
float |
Base payment |
bonus |
float |
Bonus |
total |
float |
Total (salary + bonus) |
description |
Optional[str] |
Full description (loaded on demand) |
files |
Optional[List[File]] |
Attached files (loaded on demand) |
editor_work |
Optional[float] |
Editor's payment (completed only) |
your_payment |
Optional[float] |
Writer's payment (completed only) |
File
| Field | Type | Description |
|---|---|---|
id |
int |
File ID |
name |
str |
Filename |
author |
str |
Who uploaded it |
date |
str |
Upload date |
data |
Optional[bytes] |
Cached file content |
file.get_download_url() # returns the full download URL
Configuration
API client
from py4writers import API
# With credentials:
api = API(login="user", password="pass")
# With an existing session cookie:
api = API(session="your_session_cookie")
# Custom concurrency limit:
api = API(login="user", password="pass", max_concurrent_requests=5)
Always use the context manager to ensure resources are released:
async with API(login="user", password="pass") as api:
await api.login()
# ...
# session is closed automatically
Logging
import logging
# Basic:
logging.basicConfig(level=logging.INFO)
# Verbose (see every HTTP call):
logging.basicConfig(level=logging.DEBUG)
Environment variables
from envparse import env
env.read_envfile(".env")
async with API(login=env.str("LOGIN"), password=env.str("PASSWORD")) as api:
await api.login()
.env file:
LOGIN=your_login
PASSWORD=your_password
Error handling
from py4writers.exceptions import (
FourWritersAPIError, # base class for all errors
AuthenticationError, # login failed
SessionExpiredError, # session expired, re-auth needed
NetworkError, # connection / HTTP errors (auto-retried)
ParsingError, # HTML parsing failed
OrderNotFoundError, # order not found
FileNotFoundError, # file not found
RateLimitError, # rate limit exceeded
)
try:
await api.login()
orders = await api.get_orders()
except AuthenticationError:
print("Bad credentials")
except NetworkError:
print("Network issue (already retried 3 times)")
except ParsingError:
print("Website structure may have changed")
Advanced
Retry decorator
All network-facing methods use @async_retry internally. You can use it in your own code:
from py4writers.utils import async_retry
from py4writers.exceptions import NetworkError
@async_retry(max_attempts=3, delay=1.0, backoff=2.0, exceptions=(NetworkError,))
async def my_flaky_operation():
...
Rate limiter
from py4writers.utils import RateLimiter
limiter = RateLimiter(max_concurrent=5)
async with limiter:
await do_something()
# or:
result = await limiter.execute(do_something())
Project structure
src/py4writers/
__init__.py # Public API exports
const.py # URLs, endpoints, constants
exceptions.py # Exception hierarchy
api/
api.py # API facade class
_auth.py # Authentication mixin
_orders.py # Order fetching engine
_files.py # File operations mixin
_actions.py # Order actions mixin
_enrichment.py # Shared enrichment logic
client/
aiohttp.py # HTTP client wrapper
types/
models.py # Order and File dataclasses
parsers/
order_parser.py # HTML parsing
utils/
retry.py # Retry decorator
rate_limiter.py # Semaphore rate limiter
Running tests
# Install dev dependencies:
pip install pytest pytest-asyncio
# Run:
pytest tests/ -v
Requirements
- Python 3.12+
- aiohttp >= 3.11
- beautifulsoup4 >= 4.12
- lxml >= 5.3
- certifi
License
MIT
Author
socalmy — socalmy2003@gmail.com
- PyPI: pypi.org/project/py4writers
- GitHub: github.com/soca1m/py4writers
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py4writers-1.0.0.tar.gz.
File metadata
- Download URL: py4writers-1.0.0.tar.gz
- Upload date:
- Size: 16.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b4e8c2b307c173bf407b3a36c4519417774593df9c2b8a2c9c115084ce7fb173
|
|
| MD5 |
b6aae2d3768d0586ad0ed46e8df7d960
|
|
| BLAKE2b-256 |
33359dd95336b6b53de82f11a084836b341065ef31f6ffb01116c581d91f2c20
|
File details
Details for the file py4writers-1.0.0-py3-none-any.whl.
File metadata
- Download URL: py4writers-1.0.0-py3-none-any.whl
- Upload date:
- Size: 20.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d015afcb5369f401598d367edd143d684bddc95a895e6ef1b16d31c991820845
|
|
| MD5 |
cc8075f8c1f2b7cc2911e74e4fddec0f
|
|
| BLAKE2b-256 |
0f7986da7fcf7364b50901e2a727f08a4c6a0af5672a2ef7209ea757aa3a7ea1
|