|
| 1 | +# AGENTS.md - Development Guide for KEGGAPI.jl |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +KEGGAPI.jl is a Julia package providing programmatic access to the KEGG (Kyoto Encyclopedia of Genes and Genomes) REST API. This package offers functions to retrieve biological data including pathways, compounds, genes, and genomic information. |
| 6 | + |
| 7 | +## Build, Test & Development Commands |
| 8 | + |
| 9 | +### Basic Commands |
| 10 | +```bash |
| 11 | +# Install package dependencies |
| 12 | +julia --project -e "using Pkg; Pkg.instantiate()" |
| 13 | + |
| 14 | +# Run all tests |
| 15 | +julia --project -e "using Pkg; Pkg.test()" |
| 16 | + |
| 17 | +# Build package |
| 18 | +julia --project -e "using Pkg; Pkg.build()" |
| 19 | + |
| 20 | +# Run single test file |
| 21 | +julia --project test/runtests.jl |
| 22 | + |
| 23 | +# Start Julia REPL with project |
| 24 | +julia --project |
| 25 | + |
| 26 | +# Generate documentation |
| 27 | +julia --project=docs docs/make.jl |
| 28 | +``` |
| 29 | + |
| 30 | +### Development Setup |
| 31 | +```bash |
| 32 | +# Activate development environment |
| 33 | +julia --project=dev -e "using Pkg; Pkg.develop(PackageSpec(path=pwd()))" |
| 34 | + |
| 35 | +# Add development dependencies |
| 36 | +julia --project -e "using Pkg; Pkg.add([\"Test\", \"Runic\", \"Aqua\"])" |
| 37 | + |
| 38 | +# Format code with Runic.jl |
| 39 | +julia --project -e "using Runic; Runic.format(\".\")" |
| 40 | +``` |
| 41 | + |
| 42 | +### Testing Strategy |
| 43 | + |
| 44 | +**Unit Tests (Mocked):** Test wrapper functions without API calls |
| 45 | +- `kegg_get()`, `list()`, `info()`, `get_image()`, `conv()`, `find()`, `link()` |
| 46 | + |
| 47 | +**Integration Tests (Real API):** Test core request functionality |
| 48 | +- `KEGGAPI.request()` - Must test with actual KEGG API endpoints |
| 49 | + |
| 50 | +**Test Organization:** |
| 51 | +- All tests in `test/runtests.jl` |
| 52 | +- Use `@testset` for logical groupings |
| 53 | +- Test both success and failure scenarios |
| 54 | + |
| 55 | +## Code Style Guidelines |
| 56 | + |
| 57 | +### Formatting |
| 58 | +- **Formatter:** Runic.jl (enforced in CI) |
| 59 | +- **Line Length:** No strict limit, but prefer readability |
| 60 | +- **Indentation:** 4 spaces (no tabs) |
| 61 | + |
| 62 | +### Imports & Dependencies |
| 63 | +```julia |
| 64 | +# Qualified imports preferred |
| 65 | +import HTTP: get |
| 66 | + |
| 67 | +# Standard library imports |
| 68 | +using Test |
| 69 | + |
| 70 | +# Export public API clearly |
| 71 | +export request, info, list, find, get_image, kegg_get, conv, link, save_image |
| 72 | +``` |
| 73 | + |
| 74 | +### Function & Variable Naming |
| 75 | +```julia |
| 76 | +# Functions: snake_case |
| 77 | +function kegg_get(query::Vector{String}) |
| 78 | +function request_other(url::String) |
| 79 | + |
| 80 | +# Types: PascalCase |
| 81 | +struct RequestError <: Exception |
| 82 | +struct KeggTupleList |
| 83 | + |
| 84 | +# Constants: UPPER_SNAKE_CASE |
| 85 | +const DEFAULT_CHUNK_SIZE = 10 |
| 86 | + |
| 87 | +# Variables: snake_case |
| 88 | +chunk_size = 10 |
| 89 | +response_text = request(url) |
| 90 | +``` |
| 91 | + |
| 92 | +### Type Annotations |
| 93 | +```julia |
| 94 | +# Always annotate function parameters |
| 95 | +function request(url::String) |
| 96 | +function kegg_get(query::Vector{String}, option::String = "") |
| 97 | + |
| 98 | +# Use concrete types in structs |
| 99 | +struct RequestError <: Exception |
| 100 | + message::String |
| 101 | +end |
| 102 | + |
| 103 | +# Return type annotations for public API |
| 104 | +function info(database::String)::String |
| 105 | +``` |
| 106 | + |
| 107 | +### Documentation |
| 108 | +```julia |
| 109 | +""" |
| 110 | +KEGGAPI.function_name(param1, param2) -> ReturnType |
| 111 | +
|
| 112 | +Brief description of what the function does. |
| 113 | +
|
| 114 | +Longer description if needed, explaining parameters, |
| 115 | +behavior, and any important details. |
| 116 | +
|
| 117 | +# Examples |
| 118 | +```julia-repl |
| 119 | +julia> KEGGAPI.function_name("example") |
| 120 | +"result" |
| 121 | +``` |
| 122 | +
|
| 123 | +# Arguments |
| 124 | +- `param1::String`: Description of parameter |
| 125 | +- `param2::Int`: Description with default value |
| 126 | +
|
| 127 | +# Returns |
| 128 | +- `String`: Description of return value |
| 129 | +
|
| 130 | +# Throws |
| 131 | +- `RequestError`: When API request fails |
| 132 | +""" |
| 133 | +``` |
| 134 | + |
| 135 | +### Error Handling |
| 136 | +```julia |
| 137 | +# Custom exception types |
| 138 | +struct RequestError <: Exception |
| 139 | + message::String |
| 140 | +end |
| 141 | + |
| 142 | +# Explicit error throwing |
| 143 | +if response.status != 200 |
| 144 | + throw(RequestError("Request failed with status $(response.status)")) |
| 145 | +end |
| 146 | + |
| 147 | +# Error testing in tests |
| 148 | +@test_throws RequestError KEGGAPI.info("invalid_db") |
| 149 | +``` |
| 150 | + |
| 151 | +### Control Flow & Logic |
| 152 | +```julia |
| 153 | +# Clear conditional structure |
| 154 | +if condition |
| 155 | + action() |
| 156 | +elseif other_condition |
| 157 | + other_action() |
| 158 | +else |
| 159 | + default_action() |
| 160 | +end |
| 161 | + |
| 162 | +# Prefer early returns to reduce nesting |
| 163 | +function process_data(data) |
| 164 | + if isempty(data) |
| 165 | + return nothing |
| 166 | + end |
| 167 | + |
| 168 | + # Main logic here |
| 169 | + return processed_data |
| 170 | +end |
| 171 | +``` |
| 172 | + |
| 173 | +### Comments |
| 174 | +```julia |
| 175 | +# Use comments to explain WHY, not WHAT |
| 176 | +chunk_size = 10 # KEGG API optimal batch size |
| 177 | + |
| 178 | +# Complex logic deserves explanation |
| 179 | +# Split queries into chunks to respect API rate limits |
| 180 | +for i in 1:query_chunks |
| 181 | + # Process each chunk... |
| 182 | +end |
| 183 | + |
| 184 | +# TODO comments for future improvements |
| 185 | +# TODO: Add retry logic for failed requests |
| 186 | +``` |
| 187 | + |
| 188 | +### File Organization |
| 189 | +```julia |
| 190 | +# Main module (src/KEGGAPI.jl) |
| 191 | +module KEGGAPI |
| 192 | +import HTTP: get |
| 193 | +export functions... |
| 194 | +include("Structures.jl") # Types first |
| 195 | +include("Requests.jl") # Core functionality |
| 196 | +include("specialized_functions.jl") |
| 197 | +end |
| 198 | + |
| 199 | +# Each file should have focused responsibility |
| 200 | +# Structures.jl - Type definitions |
| 201 | +# Requests.jl - HTTP request handling |
| 202 | +# Info.jl - Info-related functions |
| 203 | +``` |
| 204 | + |
| 205 | +### Testing Patterns |
| 206 | +```julia |
| 207 | +@testset "Function Group" begin |
| 208 | + @testset "specific_function" begin |
| 209 | + # Test successful case |
| 210 | + result = KEGGAPI.specific_function("valid_input") |
| 211 | + @test isa(result, ExpectedType) |
| 212 | + @test length(result) > 0 |
| 213 | + |
| 214 | + # Test error case |
| 215 | + @test_throws RequestError KEGGAPI.specific_function("invalid") |
| 216 | + end |
| 217 | +end |
| 218 | + |
| 219 | +# Integration test patterns (for KEGGAPI.request only) |
| 220 | +@testset "Integration Tests" begin |
| 221 | + @testset "real API calls" begin |
| 222 | + # Test with known working endpoint |
| 223 | + result = KEGGAPI.request("https://rest.kegg.jp/info/kegg") |
| 224 | + @test isa(result, String) |
| 225 | + @test !isempty(result) |
| 226 | + end |
| 227 | +end |
| 228 | +``` |
| 229 | + |
| 230 | +## CI/CD Pipeline |
| 231 | + |
| 232 | +### Current Status |
| 233 | +- **Testing:** Julia 1.9 + nightly on Ubuntu |
| 234 | +- **Documentation:** Auto-builds and deploys |
| 235 | +- **Dependencies:** CompatHelper for automated updates |
| 236 | +- **Releases:** TagBot for automated tagging |
| 237 | + |
| 238 | +### Planned Enhancements |
| 239 | +- **Formatting:** Runic.jl enforcement (blocks PRs) |
| 240 | +- **Extended Testing:** Julia 1.10, nightly |
| 241 | +- **Code Quality:** Aqua.jl integration |
| 242 | +- **Real API Tests:** For `KEGGAPI.request()` function |
| 243 | +- **Semantic Versioning:** Automated enforcement |
| 244 | +- **Release Notes:** Automated generation |
| 245 | + |
| 246 | +### Development Workflow |
| 247 | +1. Format code with Runic.jl before committing |
| 248 | +2. Ensure all tests pass locally |
| 249 | +3. Write tests for new functionality |
| 250 | +4. Update documentation for public API changes |
| 251 | +5. Breaking changes are acceptable with proper notification |
| 252 | + |
| 253 | +## Common Patterns & Examples |
| 254 | + |
| 255 | +### Making API Requests |
| 256 | +```julia |
| 257 | +# Always use the request() function for HTTP calls |
| 258 | +response_text = request("https://rest.kegg.jp/info/$database") |
| 259 | + |
| 260 | +# Handle errors appropriately |
| 261 | +try |
| 262 | + data = request(url) |
| 263 | + return parse_response(data) |
| 264 | +catch e |
| 265 | + if e isa RequestError |
| 266 | + # Handle API errors |
| 267 | + return default_value |
| 268 | + else |
| 269 | + rethrow(e) |
| 270 | + end |
| 271 | +end |
| 272 | +``` |
| 273 | + |
| 274 | +### Processing API Responses |
| 275 | +```julia |
| 276 | +# Split responses consistently |
| 277 | +for datum in split(response_text, "\n///\n") |
| 278 | + push!(data, datum) |
| 279 | +end |
| 280 | + |
| 281 | +# Clean up response text |
| 282 | +response_text2 = replace(response_text, r"\n///([^/]*)$" => "") |
| 283 | +``` |
| 284 | + |
| 285 | +### Rate Limiting |
| 286 | +```julia |
| 287 | +# Always include delays between API calls |
| 288 | +sleep(0.1) # 100ms delay between requests |
| 289 | +``` |
| 290 | + |
| 291 | +This guide ensures consistent, maintainable code that follows Julia best practices while respecting the KEGG API's requirements and limitations. |
0 commit comments