Django / FastAPI / Celery / Postgres
Hire Senior Python
Developers in Morocco
Most engineering is reading, not typing. We hire for the ability to open a codebase somebody else wrote five years ago and ship something safe in week two.
Your repo, your standups, your review process. Morocco on UTC+0, so your engineer is online when your team is.
You will talk to an engineer, not an SDR
01 / Diagnosis
What we find when we open your codebase
Every Python product that has shipped for a few years carries the same handful of injuries. None of this is an insult. It is what happens when a team is busy and the product is working.
Recognise three of these and we already know roughly how your first month goes.
The N+1 nobody sees
A serializer or template loops a queryset and hits the database once per row. Fine at 40 records. Unusable at 4,000.
select_related and prefetch_related, then an assertNumQueries in the test so it cannot come back.
Business logic living in the Django admin
Someone needed a button. The button became a workflow. Now a core business rule only runs if a human clicks it inside an interface built for data entry.
Move the rule into a service function the admin calls. Then the API and the scheduled job can call it too.
settings.py with a dozen environment branches
if DEBUG, if STAGING, if that one client. Nobody can say what production actually loads without running it.
One settings module, environment driven, with every difference explicit and typed.
Celery tasks that are not idempotent
A retry runs the charge twice. A redelivered message sends the email again. Silent until it is a support ticket about money.
Idempotency keys, at-least-once assumed rather than hoped against, and a dead letter queue a human actually reads.
requests called with no timeout
The default is to wait forever. One slow upstream and the worker pool is gone. This is the most common way a healthy Python service dies for reasons unrelated to its own code.
timeout= everywhere, retries with backoff, and a circuit breaker on anything you do not control.
A bare except swallowing the failure
except: pass wrapped around the flaky part. The bug is still there. It just stopped telling anyone.
Catch the exception you expect, let the rest surface, and make sure the surface reaches someone on call.
Migrations nobody will run
One migration locked a big table during business hours, or a squash went wrong. Now the team edits schema by hand and the migration history is fiction.
Split the dangerous ones, build indexes with CONCURRENTLY, and make the migration path trustworthy again before anything else ships.
No connection pooler in front of Postgres
Every worker opens its own connections. Traffic doubles and the database runs out of connections long before the application runs out of capacity.
PgBouncer in transaction mode, with the application configured to expect it.
Mutable default arguments
def add(item, basket=[]). That list is created once at import and shared by every call for the life of the process. It presents as a haunting.
None as the default. A two-character fix, and it is still in production somewhere near you.
A test suite that asserts nothing
Coverage reports 74%. The tests call the code and check that it did not raise. That is not a safety net, it is a rumour of one.
Fix the tests before touching the code they are meant to protect. Usually the honest first fortnight.
Finding 01, in full
# Before: one query for the list, then one more per row.
# 400 orders on the page means 401 round trips to Postgres.
for order in Order.objects.all():
print(order.customer.name)
# After: two queries, whatever the row count.
for order in Order.objects.select_related("customer"):
print(order.customer.name)
# And the part most people skip, so it cannot come back:
with self.assertNumQueries(2):
render_order_list()The fix is two lines. The test is why it is still fixed a year from now.
02 / Method
How we work in code we did not write
The fastest way to lose a client is to arrive and start refactoring. The order does not change.
- Week 1
Read, and ship something small
A real ticket, deliberately contained. It proves the environment works, it proves the review process works, and it gives your team something to judge before anything important is at stake.
- Week 2
Find out what the tests actually cover
Not the coverage number. Whether the suite would catch a real regression. If it would not, that is the honest first piece of work and we say so rather than build on sand.
- Week 3
Fix what is costing you now
The slow endpoint, the queue that backs up, the report that times out. Something your team feels, so the value of the engagement stops being theoretical.
- Week 4 on
Earn the right to change structure
Only after shipping safely several times do we propose anything architectural. Opinions about your architecture are cheap in week one and expensive to act on.
03 / Proof / Holloway Group
Thirty minutes a document,
down to two
~30 min
Per purchase order, before
≤2 min
Per purchase order, after
≥95%
Handled with no human
≥99%
Field-level accuracy
Purchase orders arrived as PDFs in an inbox, in whatever format each supplier felt like using. Staff opened every one, cross-referenced thousands of SKUs by hand, and retyped it into Unleashed. Senior people were spending half their day as a bridge between an email client and an ERP.
- The hard part was never reading the PDF
- Extraction is the easy half. The real problem is that nobody writes the correct product ID. A supplier types "blue widget 12pk" when the system needs SKU-7823-BL-12. Every template-based automation dies exactly there.
- So we stopped matching on strings
- The full customer and product database syncs nightly into a vector store. Incoming line items are matched by semantic similarity against real records, so human phrasing resolves to the right SKU with nobody maintaining a lookup table.
- Confidence decides who handles it
- High-confidence matches post straight through. Low-confidence line items get flagged individually in a review dashboard for a one-click fix. The team audits in seconds instead of re-entering for minutes, and accuracy is not traded for speed.
- Built to survive a bad day
- Supplier codes validated against the live ERP before anything is written. Retry logic and full error handling on the integration. Every transaction logged with 90-day retention, plus a daily report. Grafana and Prometheus for live visibility.
- Zero template maintenance
- A new supplier with a new layout needs no configuration at all. That was the design goal, because the alternative is a system that quietly rots the moment nobody is paid to maintain it.
supplier wrote: "blue widget 12pk"
ERP expects: SKU-7823-BL-12
# string match -> no result, human takes over
# regex/template -> breaks on the next supplier
# what we did: embed both, compare meaning
match = vector_store.search(line_item, top_k=1)
if match.score >= THRESHOLD:
post_to_erp(match.sku) # ~95% of lines
else:
flag_for_review(line_item) # one click, secondsThe confidence threshold is the whole design. It is what lets the system be fast without being wrong.
Read the full Holloway Group build, or the Talearnted Tutors matching engine.
04 / Screening
What we mean by senior
Everyone writes senior. Three tests, and you can run all three yourself on the call.
They have operated Python, not only shipped it
Ask what woke them up, what the cause turned out to be, and what they changed so it did not happen twice. An engineer who has only written features answers this vaguely.
They can work in a codebase they did not write
Ask how they would spend week one in a 200,000 line Django project. If the answer is refactoring, that is your answer.
They can name a decision that turned out wrong
What it cost, and what they do differently now. An engineer who cannot has either not made enough decisions or is not being straight with you.
Ask to speak to the engineer who would work with you. We put them on the call.
05 / Rates
Rates, and who this is wrong for
$4,800–$7,200
per month / one senior engineer / full time
One number, no separate management fee, no recruitment charge, no markup on hours. If they are ill, on holiday or they resign, that is our cost to carry, not a gap in your sprint.
The rate moves on seniority, how much technical leadership you need, contract length and on-call cover. If you need more than one person, a dedicated development team in Morocco is priced per seat off the same number.
Do not hire us if
You need ten developers next week
We are a small company. We would have to recruit, and we will give you a real date rather than a comfortable one.
Do not hire us if
You have a two week job
A marketplace is genuinely the right tool for that, and it will cost you less.
Do not hire us if
You want the cheapest hourly rate
We are not it, and we will not pretend to be.
Do not hire us if
You want us to disappear for three months
If you want to hand over a spec and hear nothing until delivery, we are a bad fit on purpose.
06 / Questions
Straight answers
What if my codebase is a mess?
Most are. That is the work, and it is what we screen for when we hire. The list at the top of this page is not hypothetical.
Can they work on an existing project or only new builds?
Existing, mostly. A greenfield Python project is the rare engagement. Reading someone else's Django is a specific skill and it is the one we hire for.
Which parts of the Python stack?
Django and FastAPI, Celery for background work, Postgres, Docker, and the AWS or Hetzner infrastructure underneath. If you need deep machine learning, say so early, because that is not our strength and Morocco is thin there generally.
Can I interview them first?
Yes. We do not place anyone you have not met and approved.
How big is your team?
Small, deliberately. Every engagement has senior technical oversight and you work with the people you meet. Anyone new is introduced before they start.
Why Morocco rather than Eastern Europe or LATAM?
Timezone and language. Morocco has been on UTC+0 all year since September 2026: most of a working day with Europe and four to five hours with New York, in English and French. On cost, Lemon.io puts Morocco senior rates at $30 to $41 an hour in its April 2026 report, the lowest band it tracks.
Do you work with early stage startups?
Yes, if there is a real product and a real budget. We are a poor fit for pre-funding equity work.
Who owns the code?
You do, from the first commit. IP is assigned in the contract, not on final payment, and NDAs are signed before anyone opens your repository.
Bring us the slow endpoint
Thirty minutes. Bring the actual problem, not a tidied version of it. You will get a real opinion whether or not you hire us.
Also for you
AWS engineers who have been on call / a dedicated development team in Morocco