diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..684bcdcae --- /dev/null +++ b/404.html @@ -0,0 +1,1018 @@ + + + +
+ + + + + + + + + + + + + +This API uses Google DataStore as storage, there is not local storage on Heroku or Postgres.
+We need Google DataStore because we plan to store huge amounts of activities that the user can do inside breathecode.
+Possible activities (so far): +
"breathecode_login" //every time it logs in
+"online_platform_registration" //first day using breathecode
+"public_event_attendance" //attendy on an eventbrite event
+"classroom_attendance" //when the student attent to class
+"classroom_unattendance" //when the student miss class
+"lesson_opened" //when a lessons is opened on the platform
+"office_attendance" //when the office raspberry pi detects the student
+"nps_survey_answered" //when a nps survey is answered by the student
+"exercise_success" //when student successfully tests exercise
+
Any activity has the following inputs:
+ +Get recent user activity +
+Add a new user activity (requires authentication) +
POST: activity/user/{email_or_id}
+{
+ 'slug' => 'activity_slug',
+ 'data' => 'any aditional data (string or json-encoded-string)'
+}
+
+💡 Node: You can pass the cohort in the data json object and it will be possible to filter on the activity graph like this:
+
+{
+ 'slug' => 'activity_slug',
+ 'data' => "{ \"cohort\": \"mdc-iii\" }" (json encoded string with the cohort id)
+}
+
Endpoints for the Cohort
+Get recent user activity +
+Endpoints for the coding_error's + +Add a new coding_error (requires authentication)
+POST: activity/coding_error/
+
+{
+ "user_id" => "my@email.com",
+ "slug" => "webpack_error",
+ "data" => "optional additional information about the error",
+ "message" => "file not found",
+ "name" => "module-not-found,
+ "severity" => "900",
+ "details" => "stack trace for the error as string"
+}
+
This module take care of the academic side of breathecode: Students, Cohorts, Course (aka: Certificate), Syllabus, etc. These are some of the things you can do with the breathecode.admissions API:
+TODO: finish this documentation.
+Override previous academies +
+This app is ideal for running diagnostic and reminders on the breathecode platform.
+Setup the monitor app job for once a day, this is the command: +
+Setup the monitor script job for once a day, this is the command: +
+A monitoring script is something that you want to execute recurrently withing the breathecode API, for example:
+scripts/alert_pending_leads.py
is a small python script that checks if there is FormEntry Marketing module database that are pending processing.
You can create a monitoring script to remind academy staff members about things, or to remind students about pending homework, etc.
+./breathecode/monitoring/scripts
#!/usr/bin/env python
+"""
+Alert when there are Form Entries with status = PENDING
+"""
+from breathecode.utils import ScriptNotification
+# start your code here
+
ScriptNotification
to notify for MINOR
or CRITICAL
reasons, for example:# here we are raising a notification because there are 2 pending tasks
+raise ScriptNotification("There are 2 pending taks", status='MINOR', slug="pending_tasks")
+
There are some global variables that you have available during your scripts:
+Variable name | +Value | +
---|---|
academy | +Contains the academy model object, you can use it to retrieve the current academy id like this: query.filter(academy__id=academy.id) |
+
You can test your scripts by running the following command:
+$ python manage.py run_script <file_name>
+
+# For example you can test the alert_pending_leads script like this:
+$ python manage.py run_script alert_pending_leads.py
+
The following script checks for pending leads to process:
+#!/usr/bin/env python
+"""
+Alert when there are Form Entries with status = PENDING
+"""
+from breathecode.marketing.models import FormEntry
+from django.db.models import Q
+from breathecode.utils import ScriptNotification
+
+# check the database for pending leads
+pending_leads = FormEntry.objects.filter(storage_status="PENDING").filter(Q(academy__id=academy.id) | Q(location=academy.slug))
+
+# trigger notification because pending leads were found
+if len(pending_leads) > 0:
+ raise ScriptNotification(f"Warning there are {len(pending_leads)} pending form entries", status='MINOR')
+
+# You can print this and it will show on the script results
+print("No pending leads")
+
from breathecode.monitoring.actions import run_script
+script = run_script(model.monitor_script)
+
+del script['slack_payload']
+del script['title']
+
+expected = {'details': script['details'],
+ 'severity_level': 5,
+ 'status': script['status'],
+ 'text': script['text']
+ }
+
+self.assertEqual(script, expected)
+
+self.assertEqual(self.all_monitor_script_dict(), [{
+ **self.model_to_dict(model, 'monitor_script'),
+}])
+
name | +description | +
---|---|
ENV | +Represents the current environment, can be DEVELOPMENT , TEST , and PRODUCTION |
+
LOG_LEVEL | +Represents the log level for the logging module, can be NOTSET , DEBUG , INFO , WARNING , ERROR and CRITICAL |
+
DATABASE_URL | +Represents the connection string to the database, you can read more about schema url | +
CACHE_MIDDLEWARE_MINUTES | +Represents how long an item will last in the cache | +
API_URL | +Represents the url of api rest | +
ADMIN_URL | +Represents the url of frontend of the admin | +
APP_URL | +Represents the url of frontend of the webside | +
REDIS_URL | +Represents the url of Redis | +
CELERY_TASK_SERIALIZER | +Represents the default serialization method to use. Can be pickle json , yaml , msgpack or any custom serialization methods |
+
EMAIL_NOTIFICATIONS_ENABLED | +Represents if the server can send notifications through email | +
SYSTEM_EMAIL | +Represents the email of Breathecode for support |
+
GITHUB_CLIENT_ID | +Represents the client id used for the OAuth2 with Github |
+
GITHUB_SECRET | +Represents the secret used for the OAuth2 with Github |
+
GITHUB_REDIRECT_URL | +Represents the redirect url used for the OAuth2 with Github |
+
SLACK_CLIENT_ID | +Represents the client id used for the OAuth2 with Slack |
+
SLACK_SECRET | +Represents the secret used for the OAuth2 with Slack |
+
SLACK_REDIRECT_URL | +Represents the redirect url used for the OAuth2 with Slack |
+
MAILGUN_API_KEY | +Represents the api key used for the OAuth2 with Mailgun |
+
MAILGUN_DOMAIN | +Represents the domain of Breathecode that provided Mailgun |
+
EVENTBRITE_KEY | +Represents the key used for the OAuth2 with Eventbrite |
+
FACEBOOK_VERIFY_TOKEN | +Represents the verify token used for the OAuth2 with Facebook |
+
FACEBOOK_CLIENT_ID | +Represents the client id used for the OAuth2 with Facebook |
+
FACEBOOK_SECRET | +Represents the secret used for the OAuth2 with Facebook |
+
FACEBOOK_REDIRECT_URL | +Represents the redirect url used for the OAuth2 with Facebook |
+
ACTIVE_CAMPAIGN_KEY | +Represents the key used for the OAuth2 with Active Campaign |
+
ACTIVE_CAMPAIGN_URL | +Represents the domain of Breathecode that provided Active Campaign |
+
GOOGLE_APPLICATION_CREDENTIALS | +Represents the file will be saved the service account of Google Cloud |
+
GOOGLE_SERVICE_KEY | +Represents the content of the service account used for the OAuth2 with Google Cloud |
+
GOOGLE_PROJECT_ID | +Project ID on google cloud used for the integration of the entire API | +
GOOGLE_CLOUD_KEY | +Represents the key used for the OAuth2 with Google Cloud |
+
GOOGLE_CLIENT_ID | +Represents the client id used for the OAuth2 with Google Cloud |
+
GOOGLE_SECRET | +Represents the secret used for the OAuth2 with Google Cloud |
+
GOOGLE_REDIRECT_URL | +Represents the redirect url used for the OAuth2 with Google Cloud |
+
DAILY_API_KEY | +Represents the api key used for the OAuth2 with Daily |
+
DAILY_API_URL | +Represents the domain of Breathecode that provided Daily |
+
SAVE_LEADS | +Represents if Breathecode will persist the leads | +
COMPANY_NAME | +Represents the company name | +
COMPANY_CONTACT_URL | +Represents the company contact url | +
COMPANY_LEGAL_NAME | +Represents the company legal name | +
COMPANY_ADDRESS | +Represents the company address | +
MEDIA_GALLERY_BUCKET | +Represents the bucket for the media gallery | +
DOWNLOADS_BUCKET | +Represents the bucket for the CSV files | +
PROFILE_BUCKET | +Represents the bucket for profile avatars | +
Build BreatheCode Dev docker image
Install docker desktop in your Windows, else find a guide to install Docker and Docker Compose in your linux distribution uname -a
.
# Check which dependencies you need install in your operating system
+python -m scripts.doctor
+
+# Generate the BreatheCode Dev docker image
+docker-compose build bc-dev
+
Testing inside BreatheCode Dev
# Open the BreatheCode Dev, this shell don't export the port 8000
+docker-compose run bc-dev fish
+
+# Testing
+pipenv run test ./breathecode/activity # path
+
+# Testing in parallel
+pipenv run ptest ./breathecode/activity # path
+
+# Coverage
+pipenv run cov breathecode.activity # python module path
+
+# Coverage in parallel
+pipenv run pcov breathecode.activity # python module path
+
Run BreatheCode API as docker service
# open BreatheCode API as a service and export the port 8000
+docker-compose up -d bc-dev
+
+# open the BreatheCode Dev, this shell don't export the port 8000
+docker-compose run bc-dev fish
+
+# create super user
+pipenv run python manage.py createsuperuser
+
+# Close the BreatheCode Dev
+exit
+
+# See the output of Django
+docker-compose logs -f bc-dev
+
+# open localhost:8000 to view the api
+# open localhost:8000/admin to view the admin
+
Installation in your local machine
Install docker desktop in your Windows, else find a guide to install Docker and Docker Compose in your linux distribution uname -a
.
# Check which dependencies you need install in your operating system
+python -m scripts.doctor
+
+# Setting up the redis and postgres database, you also can install manually in your local machine this databases
+docker-compose up -d redis postgres
+
+# Install and setting up your development environment (this command replace your .env file)
+python -m scripts.install
+
Testing in your local machine
# Testing
+pipenv run test ./breathecode/activity # path
+
+# Testing in parallel
+pipenv run ptest ./breathecode/activity # path
+
+# Coverage
+pipenv run cov breathecode.activity # python module path
+
+# Coverage in parallel
+pipenv run pcov breathecode.activity # python module path
+
Run BreatheCode API in your local machine
# Collect statics
+pipenv run python manage.py collectstatic --noinput
+
+# Run migrations
+pipenv run python manage.py migrate
+
+# Load fixtures (populate the database)
+pipenv run python manage.py loaddata breathecode/*/fixtures/dev_*.json
+
+# Create super user
+pipenv run python manage.py createsuperuser
+
+# Run server
+pipenv run start
+
+# open localhost:8000 to view the api
+# open localhost:8000/admin to view the admin
+
name | +description | +
---|---|
ENV | +Represents the current environment, can be DEVELOPMENT , TEST , and PRODUCTION |
+
LOG_LEVEL | +Represents the log level for the logging module, can be NOTSET , DEBUG , INFO , WARNING , ERROR and CRITICAL |
+
DATABASE_URL | +Represents the connection string to the database, you can read more about schema url | +
CACHE_MIDDLEWARE_MINUTES | +Represents how long an item will last in the cache | +
API_URL | +Represents the url of api rest | +
ADMIN_URL | +Represents the url of frontend of the admin | +
APP_URL | +Represents the url of frontend of the webside | +
REDIS_URL | +Represents the url of Redis | +
CELERY_TASK_SERIALIZER | +Represents the default serialization method to use. Can be pickle json , yaml , msgpack or any custom serialization methods |
+
EMAIL_NOTIFICATIONS_ENABLED | +Represents if the server can send notifications through email | +
SYSTEM_EMAIL | +Represents the email of Breathecode for support |
+
SAVE_LEADS | +Represents if Breathecode will persist the leads | +
COMPANY_NAME | +Represents the company name | +
COMPANY_CONTACT_URL | +Represents the company contact url | +
COMPANY_LEGAL_NAME | +Represents the company legal name | +
COMPANY_ADDRESS | +Represents the company address | +
MEDIA_GALLERY_BUCKET | +Represents the bucket for the media gallery | +
DOWNLOADS_BUCKET | +Represents the bucket for the CSV files | +
PROFILE_BUCKET | +Represents the bucket for profile avatars | +
Build BreatheCode Dev docker image
","text":"Install docker desktop in your Windows, else find a guide to install Docker and Docker Compose in your linux distribution uname -a
.
# Check which dependencies you need install in your operating system\npython -m scripts.doctor\n\n# Generate the BreatheCode Dev docker image\ndocker-compose build bc-dev\n
"},{"location":"#testing-inside-breathecode-dev","title":"Testing inside BreatheCode Dev
","text":"# Open the BreatheCode Dev, this shell don't export the port 8000\ndocker-compose run bc-dev fish\n\n# Testing\npipenv run test ./breathecode/activity # path\n# Testing in parallel\npipenv run ptest ./breathecode/activity # path\n# Coverage\npipenv run cov breathecode.activity # python module path\n# Coverage in parallel\npipenv run pcov breathecode.activity # python module path\n
"},{"location":"#run-breathecode-api-as-docker-service","title":"Run BreatheCode API as docker service
","text":"# open BreatheCode API as a service and export the port 8000\ndocker-compose up -d bc-dev\n\n# open the BreatheCode Dev, this shell don't export the port 8000\ndocker-compose run bc-dev fish\n\n# create super user\npipenv run python manage.py createsuperuser\n\n# Close the BreatheCode Dev\nexit\n# See the output of Django\ndocker-compose logs -f bc-dev\n\n# open localhost:8000 to view the api\n# open localhost:8000/admin to view the admin\n
"},{"location":"#working-in-your-local-machine-recomended","title":"Working in your local machine (recomended)","text":""},{"location":"#installation-in-your-local-machine","title":"Installation in your local machine
","text":"Install docker desktop in your Windows, else find a guide to install Docker and Docker Compose in your linux distribution uname -a
.
# Check which dependencies you need install in your operating system\npython -m scripts.doctor\n\n# Setting up the redis and postgres database, you also can install manually in your local machine this databases\ndocker-compose up -d redis postgres\n\n# Install and setting up your development environment (this command replace your .env file)\npython -m scripts.install\n
"},{"location":"#testing-in-your-local-machine","title":"Testing in your local machine
","text":"# Testing\npipenv run test ./breathecode/activity # path\n# Testing in parallel\npipenv run ptest ./breathecode/activity # path\n# Coverage\npipenv run cov breathecode.activity # python module path\n# Coverage in parallel\npipenv run pcov breathecode.activity # python module path\n
"},{"location":"#run-breathecode-api-in-your-local-machine","title":"Run BreatheCode API in your local machine
","text":"# Collect statics\npipenv run python manage.py collectstatic --noinput\n\n# Run migrations\npipenv run python manage.py migrate\n\n# Load fixtures (populate the database)\npipenv run python manage.py loaddata breathecode/*/fixtures/dev_*.json\n\n# Create super user\npipenv run python manage.py createsuperuser\n\n# Run server\npipenv run start\n\n# open localhost:8000 to view the api\n# open localhost:8000/admin to view the admin\n
"},{"location":"endponts/","title":"Enpoints Documentation","text":"This API uses Google DataStore as storage, there is not local storage on Heroku or Postgres.
We need Google DataStore because we plan to store huge amounts of activities that the user can do inside breathecode.
Possible activities (so far):
\"breathecode_login\" //every time it logs in\n\"online_platform_registration\" //first day using breathecode\n\"public_event_attendance\" //attendy on an eventbrite event\n\"classroom_attendance\" //when the student attent to class\n\"classroom_unattendance\" //when the student miss class\n\"lesson_opened\" //when a lessons is opened on the platform\n\"office_attendance\" //when the office raspberry pi detects the student\n\"nps_survey_answered\" //when a nps survey is answered by the student\n\"exercise_success\" //when student successfully tests exercise\n
Any activity has the following inputs:
'cohort',\n 'data',\n 'day',\n 'slug',\n 'user_agent',\n
"},{"location":"apps/activities/#endpoints-for-the-user","title":"Endpoints for the user","text":"Get recent user activity
GET: activity/user/{email_or_id}?slug=activity_slug\n
Add a new user activity (requires authentication)
POST: activity/user/{email_or_id}\n{\n 'slug' => 'activity_slug',\n 'data' => 'any aditional data (string or json-encoded-string)'\n}\n\n\ud83d\udca1 Node: You can pass the cohort in the data json object and it will be possible to filter on the activity graph like this:\n\n{\n 'slug' => 'activity_slug',\n 'data' => \"{ \\\"cohort\\\": \\\"mdc-iii\\\" }\" (json encoded string with the cohort id)\n}\n
Endpoints for the Cohort
Get recent user activity
GET: activity/cohort/{slug_or_id}?slug=activity_slug\n
Endpoints for the coding_error's Get recent user coding_errors\nGET: activity/coding_error/{email_or_id}?slug=activity_slug\n
Add a new coding_error (requires authentication)\nPOST: activity/coding_error/\n\n{\n \"user_id\" => \"my@email.com\",\n \"slug\" => \"webpack_error\",\n \"data\" => \"optional additional information about the error\",\n \"message\" => \"file not found\",\n \"name\" => \"module-not-found,\n \"severity\" => \"900\",\n \"details\" => \"stack trace for the error as string\"\n}\n
"},{"location":"apps/admissions/","title":"BreatheCode.Admissions","text":"This module take care of the academic side of breathecode: Students, Cohorts, Course (aka: Certificate), Syllabus, etc. These are some of the things you can do with the breathecode.admissions API:
TODO: finish this documentation.
"},{"location":"apps/admissions/#commands","title":"Commands","text":""},{"location":"apps/admissions/#sync-academies","title":"Sync academies","text":"python manage.py sync_admissions academies\n
Override previous academies
python manage.py sync_admissions academies --override\n
"},{"location":"apps/admissions/#sync-courses","title":"Sync courses","text":"python manage.py sync_admissions certificates\n
"},{"location":"apps/admissions/#sync-cohorts","title":"Sync cohorts","text":"python manage.py sync_admissions cohorts\n
"},{"location":"apps/admissions/#sync-students","title":"Sync students","text":"python manage.py sync_admissions students --limit=3\n
Limit: the number of students to sync"},{"location":"apps/monitoring/introduction/","title":"Intro to monitoring","text":"This app is ideal for running diagnostic and reminders on the breathecode platform.
"},{"location":"apps/monitoring/introduction/#installation","title":"Installation","text":"Setup the monitor app job for once a day, this is the command:
$ python manage.py monitor apps\n
Setup the monitor script job for once a day, this is the command:
$ python manage.py monitor script\n
A monitoring script is something that you want to execute recurrently withing the breathecode API, for example:
scripts/alert_pending_leads.py
is a small python script that checks if there is FormEntry Marketing module database that are pending processing.
You can create a monitoring script to remind academy staff members about things, or to remind students about pending homework, etc.
"},{"location":"apps/monitoring/scripts/#stepts-to-create-a-new-script","title":"Stepts to create a new script:","text":"./breathecode/monitoring/scripts
#!/usr/bin/env python\n\"\"\"\nAlert when there are Form Entries with status = PENDING\n\"\"\"\nfrom breathecode.utils import ScriptNotification\n# start your code here\n
ScriptNotification
to notify for MINOR
or CRITICAL
reasons, for example:# here we are raising a notification because there are 2 pending tasks\nraise ScriptNotification(\"There are 2 pending taks\", status='MINOR', slug=\"pending_tasks\")\n
5. If you don't raise any ScriptNotification and there are no other Exceptions in the script, it will be considered successfull and no notifications will trigger. 6. When a ScriptNotification has been raise the Application owner will receive a notification to the application.email and slack channel configured for notifications. 7. Check for other scripts as examples. 8. Test your script."},{"location":"apps/monitoring/scripts/#global-context","title":"Global Context","text":"There are some global variables that you have available during your scripts:
Variable name Value academy Contains the academy model object, you can use it to retrieve the current academy id like this:query.filter(academy__id=academy.id)
"},{"location":"apps/monitoring/scripts/#manually-running-your-script","title":"Manually running your script","text":"You can test your scripts by running the following command:
$ python manage.py run_script <file_name>\n\n# For example you can test the alert_pending_leads script like this:\n$ python manage.py run_script alert_pending_leads.py\n
"},{"location":"apps/monitoring/scripts/#example-script","title":"Example Script","text":"The following script checks for pending leads to process:
#!/usr/bin/env python\n\"\"\"\nAlert when there are Form Entries with status = PENDING\n\"\"\"\nfrom breathecode.marketing.models import FormEntry\nfrom django.db.models import Q\nfrom breathecode.utils import ScriptNotification\n# check the database for pending leads\npending_leads = FormEntry.objects.filter(storage_status=\"PENDING\").filter(Q(academy__id=academy.id) | Q(location=academy.slug))\n# trigger notification because pending leads were found\nif len(pending_leads) > 0:\nraise ScriptNotification(f\"Warning there are {len(pending_leads)} pending form entries\", status='MINOR')\n# You can print this and it will show on the script results\nprint(\"No pending leads\")\n
"},{"location":"apps/monitoring/scripts/#unit-testing-your-script","title":"Unit testing your script","text":"from breathecode.monitoring.actions import run_script
script = run_script(model.monitor_script)\ndel script['slack_payload']\ndel script['title']\nexpected = {'details': script['details'],\n'severity_level': 5,\n'status': script['status'],\n'text': script['text']\n}\nself.assertEqual(script, expected)\nself.assertEqual(self.all_monitor_script_dict(), [{\n**self.model_to_dict(model, 'monitor_script'),\n}])\n
"},{"location":"deployment/configuring-the-github-secrets/","title":"Configuring the Github secrets","text":"DEVELOPMENT
, TEST
, and PRODUCTION
LOG_LEVEL Represents the log level for the logging module, can be NOTSET
, DEBUG
, INFO
, WARNING
, ERROR
and CRITICAL
DATABASE_URL Represents the connection string to the database, you can read more about schema url CACHE_MIDDLEWARE_MINUTES Represents how long an item will last in the cache API_URL Represents the url of api rest ADMIN_URL Represents the url of frontend of the admin APP_URL Represents the url of frontend of the webside REDIS_URL Represents the url of Redis CELERY_TASK_SERIALIZER Represents the default serialization method to use. Can be pickle json
, yaml
, msgpack
or any custom serialization methods EMAIL_NOTIFICATIONS_ENABLED Represents if the server can send notifications through email SYSTEM_EMAIL Represents the email of Breathecode
for support GITHUB_CLIENT_ID Represents the client id used for the OAuth2 with Github
GITHUB_SECRET Represents the secret used for the OAuth2 with Github
GITHUB_REDIRECT_URL Represents the redirect url used for the OAuth2 with Github
SLACK_CLIENT_ID Represents the client id used for the OAuth2 with Slack
SLACK_SECRET Represents the secret used for the OAuth2 with Slack
SLACK_REDIRECT_URL Represents the redirect url used for the OAuth2 with Slack
MAILGUN_API_KEY Represents the api key used for the OAuth2 with Mailgun
MAILGUN_DOMAIN Represents the domain of Breathecode that provided Mailgun
EVENTBRITE_KEY Represents the key used for the OAuth2 with Eventbrite
FACEBOOK_VERIFY_TOKEN Represents the verify token used for the OAuth2 with Facebook
FACEBOOK_CLIENT_ID Represents the client id used for the OAuth2 with Facebook
FACEBOOK_SECRET Represents the secret used for the OAuth2 with Facebook
FACEBOOK_REDIRECT_URL Represents the redirect url used for the OAuth2 with Facebook
ACTIVE_CAMPAIGN_KEY Represents the key used for the OAuth2 with Active Campaign
ACTIVE_CAMPAIGN_URL Represents the domain of Breathecode that provided Active Campaign
GOOGLE_APPLICATION_CREDENTIALS Represents the file will be saved the service account of Google Cloud
GOOGLE_SERVICE_KEY Represents the content of the service account used for the OAuth2 with Google Cloud
GOOGLE_PROJECT_ID Project ID on google cloud used for the integration of the entire API GOOGLE_CLOUD_KEY Represents the key used for the OAuth2 with Google Cloud
GOOGLE_CLIENT_ID Represents the client id used for the OAuth2 with Google Cloud
GOOGLE_SECRET Represents the secret used for the OAuth2 with Google Cloud
GOOGLE_REDIRECT_URL Represents the redirect url used for the OAuth2 with Google Cloud
DAILY_API_KEY Represents the api key used for the OAuth2 with Daily
DAILY_API_URL Represents the domain of Breathecode that provided Daily
SAVE_LEADS Represents if Breathecode will persist the leads COMPANY_NAME Represents the company name COMPANY_CONTACT_URL Represents the company contact url COMPANY_LEGAL_NAME Represents the company legal name COMPANY_ADDRESS Represents the company address MEDIA_GALLERY_BUCKET Represents the bucket for the media gallery DOWNLOADS_BUCKET Represents the bucket for the CSV files PROFILE_BUCKET Represents the bucket for profile avatars"},{"location":"installation/environment-variables/","title":"Environment variables","text":"name description ENV Represents the current environment, can be DEVELOPMENT
, TEST
, and PRODUCTION
LOG_LEVEL Represents the log level for the logging module, can be NOTSET
, DEBUG
, INFO
, WARNING
, ERROR
and CRITICAL
DATABASE_URL Represents the connection string to the database, you can read more about schema url CACHE_MIDDLEWARE_MINUTES Represents how long an item will last in the cache API_URL Represents the url of api rest ADMIN_URL Represents the url of frontend of the admin APP_URL Represents the url of frontend of the webside REDIS_URL Represents the url of Redis CELERY_TASK_SERIALIZER Represents the default serialization method to use. Can be pickle json
, yaml
, msgpack
or any custom serialization methods EMAIL_NOTIFICATIONS_ENABLED Represents if the server can send notifications through email SYSTEM_EMAIL Represents the email of Breathecode
for support SAVE_LEADS Represents if Breathecode will persist the leads COMPANY_NAME Represents the company name COMPANY_CONTACT_URL Represents the company contact url COMPANY_LEGAL_NAME Represents the company legal name COMPANY_ADDRESS Represents the company address MEDIA_GALLERY_BUCKET Represents the bucket for the media gallery DOWNLOADS_BUCKET Represents the bucket for the CSV files PROFILE_BUCKET Represents the bucket for profile avatars"},{"location":"installation/fixtures/","title":"Fixtures","text":"Fixtures are fake data ideal for development.
"},{"location":"installation/fixtures/#saving-new-fixtures","title":"Saving new fixtures","text":"python manage.py dumpdata auth > ./breathecode/authenticate/fixtures/users.json\n
"},{"location":"installation/fixtures/#loading-all-fixtures","title":"Loading all fixtures","text":"pipenv run python manage.py loaddata breathecode/*/fixtures/dev_*.json\n
"},{"location":"security/capabilities/","title":"Capabilities","text":"Authenticated users must belong to at least one academy with a specific role, each role has a series of capabilities that specify what any user with that role will be \"capable\" of doing.
Authenticated methods must be decorated with the @capable_of
decorator in increase security validation. For example:
from breathecode.utils import capable_of\n@capable_of('crud_member')\ndef post(self, request, academy_id=None):\nserializer = StaffPOSTSerializer(data=request.data)\nif serializer.is_valid():\nserializer.save()\nreturn Response(serializer.data, status=status.HTTP_201_CREATED)\nreturn Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)\n
Any view decorated with the @capable_of must be used passing an academy id either:
path('academy/<int:academy_id>/member', MemberView.as_view()),
Academy
header.This list is alive, it will grow and vary over time:
CAPABILITIES = [\n{\n'slug': 'read_my_academy',\n'description': 'Read your academy information'\n},\n{\n'slug': 'crud_my_academy',\n'description': 'Read, or update your academy information (very high level, almost the academy admin)'\n},\n{\n'slug': 'crud_member',\n'description': 'Create, update or delete academy members (very high level, almost the academy admin)'\n},\n{\n'slug': 'read_member',\n'description': 'Read academy staff member information'\n},\n{\n'slug': 'crud_student',\n'description': 'Create, update or delete students'\n},\n{\n'slug': 'read_student',\n'description': 'Read student information'\n},\n{\n'slug': 'read_invite',\n'description': 'Read invites from users'\n},\n{\n'slug': 'crud_invite',\n'description': 'Create, update or delete invites from users'\n},\n{\n'slug': 'invite_resend',\n'description': 'Resent invites for user academies'\n},\n{\n'slug': 'read_assignment',\n'description': 'Read assignment information'\n},\n{\n'slug':\n'read_assignment_sensitive_details',\n'description':\n'The mentor in residence is allowed to see aditional info about the task, like the \"delivery url\"'\n},\n{\n'slug': 'read_shortlink',\n'description': 'Access the list of marketing shortlinks'\n},\n{\n'slug': 'crud_shortlink',\n'description': 'Create, update and delete marketing short links'\n},\n{\n'slug': 'crud_assignment',\n'description': 'Update assignments'\n},\n{\n'slug': 'task_delivery_details',\n'description': 'Get delivery URL for a task, that url can be sent to students for delivery'\n},\n{\n'slug': 'read_certificate',\n'description': 'List and read all academy certificates'\n},\n{\n'slug': 'crud_certificate',\n'description': 'Create, update or delete student certificates'\n},\n{\n'slug': 'read_layout',\n'description': 'Read layouts to generate new certificates'\n},\n{\n'slug': 'read_syllabus',\n'description': 'List and read syllabus information'\n},\n{\n'slug': 'crud_syllabus',\n'description': 'Create, update or delete syllabus versions'\n},\n{\n'slug': 'read_organization',\n'description': 'Read academy organization details'\n},\n{\n'slug': 'crud_organization',\n'description': 'Update, create or delete academy organization details'\n},\n{\n'slug': 'read_event',\n'description': 'List and retrieve event information'\n},\n{\n'slug': 'crud_event',\n'description': 'Create, update or delete event information'\n},\n{\n'slug': 'read_all_cohort',\n'description': 'List all the cohorts or single cohort information'\n},\n{\n'slug': 'read_single_cohort',\n'description': 'single cohort information related to a user'\n},\n{\n'slug': 'crud_cohort',\n'description': 'Create, update or delete cohort info'\n},\n{\n'slug': 'read_eventcheckin',\n'description': 'List and read all the event_checkins'\n},\n{\n'slug': 'read_survey',\n'description': 'List all the nps answers'\n},\n{\n'slug': 'crud_survey',\n'description': 'Create, update or delete surveys'\n},\n{\n'slug': 'read_nps_answers',\n'description': 'List all the nps answers'\n},\n{\n'slug': 'read_lead',\n'description': 'List all the leads'\n},\n{\n'slug': 'read_won_lead',\n'description': 'List all the won leads'\n},\n{\n'slug': 'crud_lead',\n'description': 'Create, update or delete academy leads'\n},\n{\n'slug': 'read_review',\n'description': 'Read review for a particular academy'\n},\n{\n'slug': 'crud_review',\n'description': 'Create, update or delete academy reviews'\n},\n{\n'slug': 'read_media',\n'description': 'List all the medias'\n},\n{\n'slug': 'crud_media',\n'description': 'Create, update or delete academy medias'\n},\n{\n'slug': 'read_media_resolution',\n'description': 'List all the medias resolutions'\n},\n{\n'slug': 'crud_media_resolution',\n'description': 'Create, update or delete academy media resolutions'\n},\n{\n'slug': 'read_cohort_activity',\n'description': 'Read low level activity in a cohort (attendancy, etc.)'\n},\n{\n'slug': 'generate_academy_token',\n'description': 'Create a new token only to be used by the academy'\n},\n{\n'slug': 'get_academy_token',\n'description': 'Read the academy token'\n},\n{\n'slug': 'send_reset_password',\n'description': 'Generate a temporal token and resend forgot password link'\n},\n{\n'slug': 'read_activity',\n'description': 'List all the user activities'\n},\n{\n'slug': 'crud_activity',\n'description': 'Create, update or delete a user activities'\n},\n{\n'slug': 'read_assignment',\n'description': 'List all the assignments'\n},\n{\n'slug': 'crud_assignment',\n'description': 'Create, update or delete a assignment'\n},\n{\n'slug':\n'classroom_activity',\n'description':\n'To report student activities during the classroom or cohorts (Specially meant for teachers)'\n},\n{\n'slug': 'academy_reporting',\n'description': 'Get detailed reports about the academy activity'\n},\n{\n'slug': 'generate_temporal_token',\n'description': 'Generate a temporal token to reset github credential or forgot password'\n},\n{\n'slug': 'read_mentorship_service',\n'description': 'Get all mentorship services from one academy'\n},\n{\n'slug': 'crud_mentorship_service',\n'description': 'Create, delete or update all mentorship services from one academy'\n},\n{\n'slug': 'read_mentorship_mentor',\n'description': 'Get all mentorship mentors from one academy'\n},\n{\n'slug': 'crud_mentorship_mentor',\n'description': 'Create, delete or update all mentorship mentors from one academy'\n},\n{\n'slug': 'read_mentorship_session',\n'description': 'Get all session from one academy'\n},\n{\n'slug': 'crud_mentorship_session',\n'description': 'Create, delete or update all session from one academy'\n},\n{\n'slug': 'crud_freelancer_bill',\n'description': 'Create, delete or update all freelancer bills from one academy'\n},\n{\n'slug': 'read_freelancer_bill',\n'description': 'Read all all freelancer bills from one academy'\n},\n{\n'slug': 'crud_mentorship_bill',\n'description': 'Create, delete or update all mentroship bills from one academy'\n},\n{\n'slug': 'read_mentorship_bill',\n'description': 'Read all mentroship bills from one academy'\n},\n{\n'slug': 'read_asset',\n'description': 'Read all academy registry assets'\n},\n{\n'slug': 'crud_asset',\n'description': 'Update, create and delete registry assets'\n},\n{\n'slug': 'read_tag',\n'description': 'Read marketing tags and their details'\n},\n{\n'slug': 'crud_tag',\n'description': 'Update, create and delete a marketing tag and its details'\n},\n{\n'slug': 'get_gitpod_user',\n'description': 'List gitpod user the academy is consuming'\n},\n{\n'slug': 'update_gitpod_user',\n'description': 'Update gitpod user expiration based on available information'\n},\n{\n'slug': 'read_technology',\n'description': 'Read asset technologies'\n},\n{\n'slug': 'crud_technology',\n'description': 'Update, create and delete asset technologies'\n},\n{\n'slug': 'read_keyword',\n'description': 'Read SEO keywords'\n},\n{\n'slug': 'crud_keyword',\n'description': 'Update, create and delete SEO keywords'\n},\n{\n'slug': 'read_keywordcluster',\n'description': 'Update, create and delete asset technologies'\n},\n{\n'slug': 'crud_keywordcluster',\n'description': 'Update, create and delete asset technologies'\n},\n]\n
"},{"location":"services/google_cloud/google-cloud-functions/","title":"Google Cloud Functions","text":""},{"location":"services/google_cloud/google-cloud-functions/#write-a-http-function","title":"Write a HTTP function","text":"https://cloud.google.com/functions/docs/writing/http
"},{"location":"services/google_cloud/google-cloud-functions/#see-active-functions","title":"See active functions","text":"https://console.cloud.google.com/functions/list
"},{"location":"services/google_cloud/google-cloud-functions/#testing-function","title":"Testing function","text":"https://cloud.google.com/functions/docs/testing/test-http#functions-testing-http-integration-python
"},{"location":"services/google_cloud/google-cloud-functions/#list-functions","title":"List functions","text":"Name Activator Resource Repository process-zap HTTP process-zap screenshots HTTP screenshots jefer94/screenshots resize-image HTTP resize-image breatheco-de/gcloud-resize-image thumbnail-generator Bucket media-breathecode breatheco-de/gcloud-thumbnail-generator thumbnail-generator-dev Bucket media-breathecode-dev breatheco-de/gcloud-thumbnail-generator"},{"location":"services/google_cloud/storage/","title":"Storage","text":""},{"location":"services/google_cloud/storage/#breathecode.services.google_cloud.storage.Storage","title":"Storage
","text":"Google Cloud Storage
Source code inbreathecode/services/google_cloud/storage.py
class Storage:\n\"\"\"Google Cloud Storage\"\"\"\nclient: storage.Client\ndef __init__(self) -> None:\n# from google.cloud.storage import Client\ncredentials.resolve_credentials()\nself.client = storage.Client()\ndef file(self, bucket_name: str, file_name: str) -> File:\n\"\"\"Get File object\n Args:\n bucket_name (str): Name of bucket in Google Cloud Storage\n file_name (str): Name of blob in Google Cloud Bucket\n Returns:\n File: File object\n \"\"\"\nbucket = self.client.bucket(bucket_name)\nreturn File(bucket, file_name)\n
"},{"location":"services/google_cloud/storage/#breathecode.services.google_cloud.storage.Storage.file","title":"file(bucket_name, file_name)
","text":"Get File object
Parameters:
Name Type Description Defaultbucket_name
str
Name of bucket in Google Cloud Storage
requiredfile_name
str
Name of blob in Google Cloud Bucket
requiredReturns:
Name Type DescriptionFile
File
File object
Source code inbreathecode/services/google_cloud/storage.py
def file(self, bucket_name: str, file_name: str) -> File:\n\"\"\"Get File object\n Args:\n bucket_name (str): Name of bucket in Google Cloud Storage\n file_name (str): Name of blob in Google Cloud Bucket\n Returns:\n File: File object\n \"\"\"\nbucket = self.client.bucket(bucket_name)\nreturn File(bucket, file_name)\n
"},{"location":"services/slack%20integration/icons/","title":"Icons","text":"The following icons are being used for the slack integrations https://www.pngrepo.com/collection/soft-colored-ui-icons/1
"},{"location":"signals/quickstart/","title":"Quickstart","text":""},{"location":"signals/quickstart/#signals","title":"Signals","text":"The official documentation for django signals can be found here.
At BreatheCode, signals are similar concept to \"events\", we use signals as custom \"events\" that can notify important things that happen in one app to all the other app's (if they are listening).
For example: When a student drops from a cohort
There is a signal to notify when a student educational status gets updated
, this is useful because other application may react to it. Here is the signal being initialized, here is being triggered/dispatched when a student gets saved and this is an example where the signal is being received on the breathecode.marketing.app to trigger some additional tasks within the system.
Inside the breathecode team, we see signals for asynchronous processing of any side effects, we try to focus on them for communication between apps only.
"},{"location":"signals/quickstart/#declare-a-new-signal","title":"Declare a new signal","text":"You have many examples that you can find inside the code, each breathecode app has a file signals.py
that contains all the signals dispatched by that app. If the file does not exist within one of the apps, and you need to create a signal for that app, you can create the file yourself.
If you wanted to create a signal for when a cohort is saved, you should start by initializing it inside breathecode/admissions/signals.py
like this:
from django.dispatch import Signal\ncohort_saved = Signal()\n
"},{"location":"signals/quickstart/#dispatching-a-signal","title":"Dispatching a signal","text":"All the initialized signals are available on the same application signals.py
file, if the signal you want to dispatch is not there, you should probably declare a new one.
After the signal is initialized, it can be dispatched anywhere withing the same app, for example inside a serializer create method like this:
from .signals import cohort_saved\nclass CohortSerializer(CohortSerializerMixin):\ndef create(self, validated_data):\ncohort = Cohort.objects.create(**validated_data, **self.context)\ncohort_saved.send(instance=self, sender=CohortUser)\nreturn cohort\n
"},{"location":"signals/quickstart/#receiving-a-signal","title":"Receiving a signal","text":"All django applications can subscribe to receive a signal, even if those signals are coming from another app, but you should always add your receiving code inside the receivers.py of the app that will react to the signal.
The following code will receive the cohort_saved
signal and print on the screen if its being created or updated.
Note: Its a good idea to always connect receivers to tasks, that way you can asynconosly pospone any processing that you will do after the cohort its created.
from breathecode.admissions.signals import student_edu_status_updated, cohort_saved\nfrom .models import FormEntry, ActiveCampaignAcademy\nfrom .tasks import add_cohort_task_to_student, add_cohort_slug_as_acp_tag\n@receiver(cohort_saved, sender=Cohort)\ndef cohort_post_save(sender, instance, created, *args, **kwargs):\nif created:\nprint(f\"The cohort {instance.id} was just created\")\n# you can call a task from task.py here.\nelse:\nprint(f\"The cohort {instance.id} was just updated\")\n
"},{"location":"testing/runing-tests/","title":"Runing tests","text":""},{"location":"testing/runing-tests/#run-tests","title":"Run tests","text":"pipenv run test ./breathecode/\n
"},{"location":"testing/runing-tests/#run-tests-in-parallel","title":"Run tests in parallel","text":"pipenv run ptest ./breathecode/\n
"},{"location":"testing/runing-tests/#run-coverage","title":"Run coverage","text":"pipenv run cov breathecode\n
"},{"location":"testing/runing-tests/#run-coverage-in-parallel","title":"Run coverage in parallel","text":"pipenv run pcov breathecode\n
"},{"location":"testing/runing-tests/#testing-inside-docker-fallback-option","title":"Testing inside Docker (fallback option)","text":"pipenv run doctor
or python -m scripts.doctor
.uname -a
.pipenv run docker_build_shell
.docker-compose run bc-shell
pipenv run test
, pipenv run ptest
, pipenv run cov
or pipenv run pcov
.Cache
","text":"Mixin with the purpose of cover all the related with cache
Source code inbreathecode/tests/mixins/breathecode_mixin/cache.py
class Cache:\n\"\"\"Mixin with the purpose of cover all the related with cache\"\"\"\nclear = CacheMixin.clear_cache\n_parent: APITestCase\n_bc: interfaces.BreathecodeInterface\ndef __init__(self, parent, bc: interfaces.BreathecodeInterface) -> None:\nself._parent = parent\nself._bc = bc\n
"},{"location":"testing/mixins/bc-check/","title":"bc.check","text":""},{"location":"testing/mixins/bc-check/#breathecode.tests.mixins.breathecode_mixin.check.Check","title":"Check
","text":"Mixin with the purpose of cover all the related with the custom asserts
Source code inbreathecode/tests/mixins/breathecode_mixin/check.py
class Check:\n\"\"\"Mixin with the purpose of cover all the related with the custom asserts\"\"\"\nsha256 = Sha256Mixin.assertHash\ntoken = TokenMixin.assertToken\n_parent: APITestCase\n_bc: interfaces.BreathecodeInterface\ndef __init__(self, parent, bc: interfaces.BreathecodeInterface) -> None:\nself._parent = parent\nself._bc = bc\ndef datetime_in_range(self, start: datetime, end: datetime, date: datetime) -> None:\n\"\"\"\n Check if a range if between start and end argument.\n Usage:\n ```py\n from django.utils import timezone\n start = timezone.now()\n in_range = timezone.now()\n end = timezone.now()\n out_of_range = timezone.now()\n # pass because this datetime is between start and end\n self.bc.check.datetime_in_range(start, end, in_range) # \ud83d\udfe2\n # fail because this datetime is not between start and end\n self.bc.check.datetime_in_range(start, end, out_of_range) # \ud83d\udd34\n ```\n \"\"\"\nself._parent.assertLess(start, date)\nself._parent.assertGreater(end, date)\ndef partial_equality(self, first: dict | list[dict], second: dict | list[dict]) -> None:\n\"\"\"\n Fail if the two objects are partially unequal as determined by the '==' operator.\n Usage:\n ```py\n obj1 = {'key1': 1, 'key2': 2}\n obj2 = {'key2': 2, 'key3': 1}\n obj3 = {'key2': 2}\n # it's fail because the key3 is not in the obj1\n self.bc.check.partial_equality(obj1, obj2) # \ud83d\udd34\n # it's fail because the key1 is not in the obj2\n self.bc.check.partial_equality(obj2, obj1) # \ud83d\udd34\n # it's pass because the key2 exists in the obj1\n self.bc.check.partial_equality(obj1, obj3) # \ud83d\udfe2\n # it's pass because the key2 exists in the obj2\n self.bc.check.partial_equality(obj2, obj3) # \ud83d\udfe2\n # it's fail because the key1 is not in the obj3\n self.bc.check.partial_equality(obj3, obj1) # \ud83d\udd34\n # it's fail because the key3 is not in the obj3\n self.bc.check.partial_equality(obj3, obj2) # \ud83d\udd34\n ```\n \"\"\"\nassert type(first) == type(second)\nif isinstance(first, list):\nassert len(first) == len(second)\noriginal = []\nfor i in range(0, len(first)):\noriginal.append(self._fill_partial_equality(first[i], second[i]))\nelse:\noriginal = self._fill_partial_equality(first, second)\nself._parent.assertEqual(original, second)\ndef calls(self, first: list[call], second: list[call]) -> None:\n\"\"\"\n Fail if the two objects are partially unequal as determined by the '==' operator.\n Usage:\n ```py\n self.bc.check.calls(mock.call_args_list, [call(1, 2, a=3, b=4)])\n ```\n \"\"\"\n# assert len(first) == len(second), f'not have same length than {first}\\n{second}'\nself._parent.assertEqual(len(first),\nlen(second),\nmsg=f'Does not have same length\\n\\n{first}\\n\\n!=\\n\\n{second}')\nfor i in range(0, len(first)):\nself._parent.assertEqual(first[i].args, second[i].args, msg=f'args in index {i} does not match')\nself._parent.assertEqual(first[i].kwargs,\nsecond[i].kwargs,\nmsg=f'kwargs in index {i} does not match')\ndef _fill_partial_equality(self, first: dict, second: dict) -> dict:\noriginal = {}\nfor key in second.keys():\noriginal[key] = second[key]\nreturn original\ndef queryset_of(self, query: Any, model: Model) -> None:\n\"\"\"\n Check if the first argument is a queryset of a models provided as second argument.\n Usage:\n ```py\n from breathecode.admissions.models import Cohort, Academy\n self.bc.database.create(cohort=1)\n collection = []\n queryset = Cohort.objects.filter()\n # pass because the first argument is a QuerySet and it's type Cohort\n self.bc.check.queryset_of(queryset, Cohort) # \ud83d\udfe2\n # fail because the first argument is a QuerySet and it is not type Academy\n self.bc.check.queryset_of(queryset, Academy) # \ud83d\udd34\n # fail because the first argument is not a QuerySet\n self.bc.check.queryset_of(collection, Academy) # \ud83d\udd34\n ```\n \"\"\"\nif not isinstance(query, QuerySet):\nself._parent.fail('The first argument is not a QuerySet')\nif query.model != model:\nself._parent.fail(f'The QuerySet is type {query.model.__name__} instead of {model.__name__}')\ndef queryset_with_pks(self, query: Any, pks: list[int]) -> None:\n\"\"\"\n Check if the queryset have the following primary keys.\n Usage:\n ```py\n from breathecode.admissions.models import Cohort, Academy\n self.bc.database.create(cohort=1)\n collection = []\n queryset = Cohort.objects.filter()\n # pass because the QuerySet has the primary keys 1\n self.bc.check.queryset_with_pks(queryset, [1]) # \ud83d\udfe2\n # fail because the QuerySet has the primary keys 1 but the second argument is empty\n self.bc.check.queryset_with_pks(queryset, []) # \ud83d\udd34\n ```\n \"\"\"\nif not isinstance(query, QuerySet):\nself._parent.fail('The first argument is not a QuerySet')\nself._parent.assertEqual([x.pk for x in query], pks)\ndef list_with_pks(self, query: Any, pks: list[int]) -> None:\n\"\"\"\n Check if the list have the following primary keys.\n Usage:\n ```py\n from breathecode.admissions.models import Cohort, Academy\n model = self.bc.database.create(cohort=1)\n collection = [model.cohort]\n # pass because the QuerySet has the primary keys 1\n self.bc.check.list_with_pks(collection, [1]) # \ud83d\udfe2\n # fail because the QuerySet has the primary keys 1 but the second argument is empty\n self.bc.check.list_with_pks(collection, []) # \ud83d\udd34\n ```\n \"\"\"\nif not isinstance(query, list):\nself._parent.fail('The first argument is not a list')\nself._parent.assertEqual([x.pk for x in query], pks)\n
"},{"location":"testing/mixins/bc-check/#breathecode.tests.mixins.breathecode_mixin.check.Check.calls","title":"calls(first, second)
","text":"Fail if the two objects are partially unequal as determined by the '==' operator.
Usage:
self.bc.check.calls(mock.call_args_list, [call(1, 2, a=3, b=4)])\n
Source code in breathecode/tests/mixins/breathecode_mixin/check.py
def calls(self, first: list[call], second: list[call]) -> None:\n\"\"\"\n Fail if the two objects are partially unequal as determined by the '==' operator.\n Usage:\n ```py\n self.bc.check.calls(mock.call_args_list, [call(1, 2, a=3, b=4)])\n ```\n \"\"\"\n# assert len(first) == len(second), f'not have same length than {first}\\n{second}'\nself._parent.assertEqual(len(first),\nlen(second),\nmsg=f'Does not have same length\\n\\n{first}\\n\\n!=\\n\\n{second}')\nfor i in range(0, len(first)):\nself._parent.assertEqual(first[i].args, second[i].args, msg=f'args in index {i} does not match')\nself._parent.assertEqual(first[i].kwargs,\nsecond[i].kwargs,\nmsg=f'kwargs in index {i} does not match')\n
"},{"location":"testing/mixins/bc-check/#breathecode.tests.mixins.breathecode_mixin.check.Check.datetime_in_range","title":"datetime_in_range(start, end, date)
","text":"Check if a range if between start and end argument.
Usage:
from django.utils import timezone\nstart = timezone.now()\nin_range = timezone.now()\nend = timezone.now()\nout_of_range = timezone.now()\n# pass because this datetime is between start and end\nself.bc.check.datetime_in_range(start, end, in_range) # \ud83d\udfe2\n# fail because this datetime is not between start and end\nself.bc.check.datetime_in_range(start, end, out_of_range) # \ud83d\udd34\n
Source code in breathecode/tests/mixins/breathecode_mixin/check.py
def datetime_in_range(self, start: datetime, end: datetime, date: datetime) -> None:\n\"\"\"\n Check if a range if between start and end argument.\n Usage:\n ```py\n from django.utils import timezone\n start = timezone.now()\n in_range = timezone.now()\n end = timezone.now()\n out_of_range = timezone.now()\n # pass because this datetime is between start and end\n self.bc.check.datetime_in_range(start, end, in_range) # \ud83d\udfe2\n # fail because this datetime is not between start and end\n self.bc.check.datetime_in_range(start, end, out_of_range) # \ud83d\udd34\n ```\n \"\"\"\nself._parent.assertLess(start, date)\nself._parent.assertGreater(end, date)\n
"},{"location":"testing/mixins/bc-check/#breathecode.tests.mixins.breathecode_mixin.check.Check.list_with_pks","title":"list_with_pks(query, pks)
","text":"Check if the list have the following primary keys.
Usage:
from breathecode.admissions.models import Cohort, Academy\nmodel = self.bc.database.create(cohort=1)\ncollection = [model.cohort]\n# pass because the QuerySet has the primary keys 1\nself.bc.check.list_with_pks(collection, [1]) # \ud83d\udfe2\n# fail because the QuerySet has the primary keys 1 but the second argument is empty\nself.bc.check.list_with_pks(collection, []) # \ud83d\udd34\n
Source code in breathecode/tests/mixins/breathecode_mixin/check.py
def list_with_pks(self, query: Any, pks: list[int]) -> None:\n\"\"\"\n Check if the list have the following primary keys.\n Usage:\n ```py\n from breathecode.admissions.models import Cohort, Academy\n model = self.bc.database.create(cohort=1)\n collection = [model.cohort]\n # pass because the QuerySet has the primary keys 1\n self.bc.check.list_with_pks(collection, [1]) # \ud83d\udfe2\n # fail because the QuerySet has the primary keys 1 but the second argument is empty\n self.bc.check.list_with_pks(collection, []) # \ud83d\udd34\n ```\n \"\"\"\nif not isinstance(query, list):\nself._parent.fail('The first argument is not a list')\nself._parent.assertEqual([x.pk for x in query], pks)\n
"},{"location":"testing/mixins/bc-check/#breathecode.tests.mixins.breathecode_mixin.check.Check.partial_equality","title":"partial_equality(first, second)
","text":"Fail if the two objects are partially unequal as determined by the '==' operator.
Usage:
obj1 = {'key1': 1, 'key2': 2}\nobj2 = {'key2': 2, 'key3': 1}\nobj3 = {'key2': 2}\n# it's fail because the key3 is not in the obj1\nself.bc.check.partial_equality(obj1, obj2) # \ud83d\udd34\n# it's fail because the key1 is not in the obj2\nself.bc.check.partial_equality(obj2, obj1) # \ud83d\udd34\n# it's pass because the key2 exists in the obj1\nself.bc.check.partial_equality(obj1, obj3) # \ud83d\udfe2\n# it's pass because the key2 exists in the obj2\nself.bc.check.partial_equality(obj2, obj3) # \ud83d\udfe2\n# it's fail because the key1 is not in the obj3\nself.bc.check.partial_equality(obj3, obj1) # \ud83d\udd34\n# it's fail because the key3 is not in the obj3\nself.bc.check.partial_equality(obj3, obj2) # \ud83d\udd34\n
Source code in breathecode/tests/mixins/breathecode_mixin/check.py
def partial_equality(self, first: dict | list[dict], second: dict | list[dict]) -> None:\n\"\"\"\n Fail if the two objects are partially unequal as determined by the '==' operator.\n Usage:\n ```py\n obj1 = {'key1': 1, 'key2': 2}\n obj2 = {'key2': 2, 'key3': 1}\n obj3 = {'key2': 2}\n # it's fail because the key3 is not in the obj1\n self.bc.check.partial_equality(obj1, obj2) # \ud83d\udd34\n # it's fail because the key1 is not in the obj2\n self.bc.check.partial_equality(obj2, obj1) # \ud83d\udd34\n # it's pass because the key2 exists in the obj1\n self.bc.check.partial_equality(obj1, obj3) # \ud83d\udfe2\n # it's pass because the key2 exists in the obj2\n self.bc.check.partial_equality(obj2, obj3) # \ud83d\udfe2\n # it's fail because the key1 is not in the obj3\n self.bc.check.partial_equality(obj3, obj1) # \ud83d\udd34\n # it's fail because the key3 is not in the obj3\n self.bc.check.partial_equality(obj3, obj2) # \ud83d\udd34\n ```\n \"\"\"\nassert type(first) == type(second)\nif isinstance(first, list):\nassert len(first) == len(second)\noriginal = []\nfor i in range(0, len(first)):\noriginal.append(self._fill_partial_equality(first[i], second[i]))\nelse:\noriginal = self._fill_partial_equality(first, second)\nself._parent.assertEqual(original, second)\n
"},{"location":"testing/mixins/bc-check/#breathecode.tests.mixins.breathecode_mixin.check.Check.queryset_of","title":"queryset_of(query, model)
","text":"Check if the first argument is a queryset of a models provided as second argument.
Usage:
from breathecode.admissions.models import Cohort, Academy\nself.bc.database.create(cohort=1)\ncollection = []\nqueryset = Cohort.objects.filter()\n# pass because the first argument is a QuerySet and it's type Cohort\nself.bc.check.queryset_of(queryset, Cohort) # \ud83d\udfe2\n# fail because the first argument is a QuerySet and it is not type Academy\nself.bc.check.queryset_of(queryset, Academy) # \ud83d\udd34\n# fail because the first argument is not a QuerySet\nself.bc.check.queryset_of(collection, Academy) # \ud83d\udd34\n
Source code in breathecode/tests/mixins/breathecode_mixin/check.py
def queryset_of(self, query: Any, model: Model) -> None:\n\"\"\"\n Check if the first argument is a queryset of a models provided as second argument.\n Usage:\n ```py\n from breathecode.admissions.models import Cohort, Academy\n self.bc.database.create(cohort=1)\n collection = []\n queryset = Cohort.objects.filter()\n # pass because the first argument is a QuerySet and it's type Cohort\n self.bc.check.queryset_of(queryset, Cohort) # \ud83d\udfe2\n # fail because the first argument is a QuerySet and it is not type Academy\n self.bc.check.queryset_of(queryset, Academy) # \ud83d\udd34\n # fail because the first argument is not a QuerySet\n self.bc.check.queryset_of(collection, Academy) # \ud83d\udd34\n ```\n \"\"\"\nif not isinstance(query, QuerySet):\nself._parent.fail('The first argument is not a QuerySet')\nif query.model != model:\nself._parent.fail(f'The QuerySet is type {query.model.__name__} instead of {model.__name__}')\n
"},{"location":"testing/mixins/bc-check/#breathecode.tests.mixins.breathecode_mixin.check.Check.queryset_with_pks","title":"queryset_with_pks(query, pks)
","text":"Check if the queryset have the following primary keys.
Usage:
from breathecode.admissions.models import Cohort, Academy\nself.bc.database.create(cohort=1)\ncollection = []\nqueryset = Cohort.objects.filter()\n# pass because the QuerySet has the primary keys 1\nself.bc.check.queryset_with_pks(queryset, [1]) # \ud83d\udfe2\n# fail because the QuerySet has the primary keys 1 but the second argument is empty\nself.bc.check.queryset_with_pks(queryset, []) # \ud83d\udd34\n
Source code in breathecode/tests/mixins/breathecode_mixin/check.py
def queryset_with_pks(self, query: Any, pks: list[int]) -> None:\n\"\"\"\n Check if the queryset have the following primary keys.\n Usage:\n ```py\n from breathecode.admissions.models import Cohort, Academy\n self.bc.database.create(cohort=1)\n collection = []\n queryset = Cohort.objects.filter()\n # pass because the QuerySet has the primary keys 1\n self.bc.check.queryset_with_pks(queryset, [1]) # \ud83d\udfe2\n # fail because the QuerySet has the primary keys 1 but the second argument is empty\n self.bc.check.queryset_with_pks(queryset, []) # \ud83d\udd34\n ```\n \"\"\"\nif not isinstance(query, QuerySet):\nself._parent.fail('The first argument is not a QuerySet')\nself._parent.assertEqual([x.pk for x in query], pks)\n
"},{"location":"testing/mixins/bc-database/","title":"bc.database","text":""},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database","title":"Database
","text":"Mixin with the purpose of cover all the related with the database
Source code inbreathecode/tests/mixins/breathecode_mixin/database.py
class Database:\n\"\"\"Mixin with the purpose of cover all the related with the database\"\"\"\n_cache: dict[str, Model] = {}\n_parent: APITestCase\n_bc: interfaces.BreathecodeInterface\nhow_many = 0\ndef __init__(self, parent, bc: interfaces.BreathecodeInterface) -> None:\nself._parent = parent\nself._bc = bc\ndef reset_queries(self):\nreset_queries()\n# @override_settings(DEBUG=True)\ndef get_queries(self, db='default'):\nreturn [query['sql'] for query in connections[db].queries]\n# @override_settings(DEBUG=True)\ndef print_queries(self, db='default'):\nfor query in connections[db].queries:\nprint(f'{query[\"time\"]} {query[\"sql\"]}\\n')\n@classmethod\ndef get_model(cls, path: str) -> Model:\n\"\"\"\n Return the model matching the given app_label and model_name.\n As a shortcut, app_label may be in the form <app_label>.<model_name>.\n model_name is case-insensitive.\n Raise LookupError if no application exists with this label, or no\n model exists with this name in the application. Raise ValueError if\n called with a single argument that doesn't contain exactly one dot.\n Usage:\n ```py\n # class breathecode.admissions.models.Cohort\n Cohort = self.bc.database.get_model('admissions.Cohort')\n ```\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n \"\"\"\nif path in cls._cache:\nreturn cls._cache[path]\napp_label, model_name = path.split('.')\ncls._cache[path] = apps.get_model(app_label, model_name)\nreturn cls._cache[path]\ndef list_of(self, path: str, dict: bool = True) -> list[Model | dict[str, Any]]:\n\"\"\"\n This is a wrapper for `Model.objects.filter()`, get a list of values of models as `list[dict]` if\n `dict=True` else get a list of `Model` instances.\n Usage:\n ```py\n # get all the Cohort as list of dict\n self.bc.database.get('admissions.Cohort')\n # get all the Cohort as list of instances of model\n self.bc.database.get('admissions.Cohort', dict=False)\n ```\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n - dict(`bool`): if true return dict of values of model else return model instance.\n \"\"\"\nmodel = Database.get_model(path)\nresult = model.objects.filter()\nif dict:\nresult = [ModelsMixin.remove_dinamics_fields(self, data.__dict__.copy()) for data in result]\nreturn result\n@database_sync_to_async\ndef async_list_of(self, path: str, dict: bool = True) -> list[Model | dict[str, Any]]:\n\"\"\"\n This is a wrapper for `Model.objects.filter()`, get a list of values of models as `list[dict]` if\n `dict=True` else get a list of `Model` instances.\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n - dict(`bool`): if true return dict of values of model else return model instance.\n \"\"\"\nreturn self.list_of(path, dict)\ndef delete(self, path: str, pk: Optional[int | str] = None) -> tuple[int, dict[str, int]]:\n\"\"\"\n This is a wrapper for `Model.objects.filter(pk=pk).delete()`, delete a element if `pk` is provided else\n all the entries.\n Usage:\n ```py\n # create 19110911 cohorts \ud83e\uddbe\n self.bc.database.create(cohort=19110911)\n # exists 19110911 cohorts \ud83e\uddbe\n self.assertEqual(self.bc.database.count('admissions.Cohort'), 19110911)\n # remove all the cohorts\n self.bc.database.delete(10)\n # exists 19110910 cohorts\n self.assertEqual(self.bc.database.count('admissions.Cohort'), 19110910)\n ```\n # remove all the cohorts\n self.bc.database.delete()\n # exists 0 cohorts\n self.assertEqual(self.bc.database.count('admissions.Cohort'), 0)\n ```\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n - pk(`str | int`): primary key of model.\n \"\"\"\nlookups = {'pk': pk} if pk else {}\nmodel = Database.get_model(path)\nreturn model.objects.filter(**lookups).delete()\ndef get(self, path: str, pk: int or str, dict: bool = True) -> Model | dict[str, Any]:\n\"\"\"\n This is a wrapper for `Model.objects.filter(pk=pk).first()`, get the values of model as `dict` if\n `dict=True` else get the `Model` instance.\n Usage:\n ```py\n # get the Cohort with the pk 1 as dict\n self.bc.database.get('admissions.Cohort', 1)\n # get the Cohort with the pk 1 as instance of model\n self.bc.database.get('admissions.Cohort', 1, dict=False)\n ```\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n - pk(`str | int`): primary key of model.\n - dict(`bool`): if true return dict of values of model else return model instance.\n \"\"\"\nmodel = Database.get_model(path)\nresult = model.objects.filter(pk=pk).first()\nif dict:\nresult = ModelsMixin.remove_dinamics_fields(self, result.__dict__.copy())\nreturn result\n@database_sync_to_async\ndef async_get(self, path: str, pk: int | str, dict: bool = True) -> Model | dict[str, Any]:\n\"\"\"\n This is a wrapper for `Model.objects.filter(pk=pk).first()`, get the values of model as `dict` if\n `dict=True` else get the `Model` instance.\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n - pk(`str | int`): primary key of model.\n - dict(`bool`): if true return dict of values of model else return model instance.\n \"\"\"\nreturn self.get(path, pk, dict)\ndef count(self, path: str) -> int:\n\"\"\"\n This is a wrapper for `Model.objects.count()`, get how many instances of this `Model` are saved.\n Usage:\n ```py\n self.bc.database.count('admissions.Cohort')\n ```\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n \"\"\"\nmodel = Database.get_model(path)\nreturn model.objects.count()\n@database_sync_to_async\ndef async_count(self, path: str) -> int:\n\"\"\"\n This is a wrapper for `Model.objects.count()`, get how many instances of this `Model` are saved.\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n \"\"\"\nreturn self.count(path)\n@cache\ndef _get_models(self) -> list[Model]:\nvalues = {}\nfor key in apps.app_configs:\nvalues[key] = apps.get_app_config(key).get_models()\nreturn values\ndef camel_case_to_snake_case(self, name):\nname = re.sub('(.)([A-Z][a-z]+)', r'\\1_\\2', name)\nreturn re.sub('([a-z0-9])([A-Z])', r'\\1_\\2', name).lower()\ndef _get_model_field_info(self, model, key):\nattr = getattr(model, key)\nmeta = vars(attr)['field'].related_model._meta\nmodel = vars(attr)['field'].related_model\nblank = attr.field.blank\nnull = attr.field.null\nresult = {\n'field': key,\n'blank': blank,\n'null': null,\n'app_name': meta.app_label,\n'model_name': meta.object_name,\n'handler': attr,\n'model': model,\n}\nif hasattr(attr, 'through'):\nresult['custom_through'] = '_' not in attr.through.__name__\nresult['through_fields'] = attr.rel.through_fields\nreturn result\n@cache\ndef _get_models_descriptors(self) -> list[Model]:\nvalues = {}\napps = self._get_models()\nfor app_key in apps:\nvalues[app_key] = {}\nmodels = apps[app_key]\nfor model in models:\nvalues[app_key][model.__name__] = {}\nvalues[app_key][model.__name__]['meta'] = {\n'app_name': model._meta.app_label,\n'model_name': model._meta.object_name,\n'model': model,\n}\nvalues[app_key][model.__name__]['to_one'] = [\nself._get_model_field_info(model, x) for x in dir(model)\nif isinstance(getattr(model, x), ForwardManyToOneDescriptor)\n]\nvalues[app_key][model.__name__]['to_many'] = [\nself._get_model_field_info(model, x) for x in dir(model)\nif isinstance(getattr(model, x), ManyToManyDescriptor)\n]\nreturn values\n@cache\ndef _get_models_dependencies(self) -> list[Model]:\nvalues = {}\ndescriptors = self._get_models_descriptors()\nfor app_key in descriptors:\nfor descriptor_key in descriptors[app_key]:\ndescriptor = descriptors[app_key][descriptor_key]\nif app_key not in values:\nvalues[app_key] = set()\nprimary_values = values[app_key]['primary'] if 'primary' in values[app_key] else []\nsecondary_values = values[app_key]['secondary'] if 'secondary' in values[app_key] else []\nvalues[app_key] = {\n'primary': {\n*primary_values, *[\nx['app_name']\nfor x in descriptor['to_one'] if x['app_name'] != app_key and x['null'] == False\n], *[\nx['app_name']\nfor x in descriptor['to_many'] if x['app_name'] != app_key and x['null'] == False\n]\n},\n'secondary': {\n*secondary_values, *[\nx['app_name']\nfor x in descriptor['to_one'] if x['app_name'] != app_key and x['null'] == True\n], *[\nx['app_name']\nfor x in descriptor['to_many'] if x['app_name'] != app_key and x['null'] == True\n]\n},\n}\nreturn values\ndef _sort_models_handlers(self,\ndependencies_resolved=None,\nprimary_values=None,\nsecondary_values=None,\nprimary_dependencies=None,\nsecondary_dependencies=None,\nconsume_primary=True) -> list[Model]:\ndependencies_resolved = dependencies_resolved or set()\nprimary_values = primary_values or []\nsecondary_values = secondary_values or []\nif not primary_dependencies and not secondary_dependencies:\ndependencies = self._get_models_dependencies()\nprimary_dependencies = {}\nfor x in dependencies:\nprimary_dependencies[x] = dependencies[x]['primary']\nsecondary_dependencies = {}\nfor x in dependencies:\nsecondary_dependencies[x] = dependencies[x]['secondary']\nfor dependency in dependencies_resolved:\nfor key in primary_dependencies:\nif dependency in primary_dependencies[key]:\nprimary_dependencies[key].remove(dependency)\nprimary_found = [\nx for x in [y for y in primary_dependencies if y not in dependencies_resolved]\nif len(primary_dependencies[x]) == 0\n]\nfor x in primary_found:\ndependencies_resolved.add(x)\nsecondary_found = [\nx for x in [y for y in secondary_dependencies if y not in dependencies_resolved]\nif len(secondary_dependencies[x]) == 0\n]\nif consume_primary and primary_found:\nprimary_values.append(primary_found)\nelif not consume_primary and secondary_found:\nsecondary_values.append(secondary_found)\nfor x in primary_found:\ndel primary_dependencies[x]\nfor dependency in primary_dependencies:\nif x in primary_dependencies[dependency]:\nprimary_dependencies[dependency].remove(x)\nif primary_dependencies:\nreturn self._sort_models_handlers(dependencies_resolved,\nprimary_values,\nsecondary_values,\nprimary_dependencies,\nsecondary_dependencies,\nconsume_primary=True)\nif secondary_dependencies:\nreturn primary_values, [x for x in secondary_dependencies if len(secondary_dependencies[x])]\nreturn primary_values, secondary_values\n@cache\ndef _get_models_handlers(self) -> list[Model]:\narguments = {}\narguments_banned = set()\norder, deferred = self._sort_models_handlers()\ndescriptors = self._get_models_descriptors()\ndef manage_model(models, descriptor, *args, **kwargs):\nmodel_field_name = self.camel_case_to_snake_case(descriptor['meta']['model_name'])\napp_name = descriptor['meta']['app_name']\nmodel_name = descriptor['meta']['model_name']\nif model_field_name in kwargs and f'{app_name}__{model_field_name}' in kwargs:\nraise Exception(f'Exists many apps with the same model name `{model_name}`, please use '\nf'`{app_name}__{model_field_name}` instead of `{model_field_name}`')\narg = False\nif f'{app_name}__{model_field_name}' in kwargs:\narg = kwargs[f'{app_name}__{model_field_name}']\nelif model_field_name in kwargs:\narg = kwargs[model_field_name]\nif not model_field_name in models and is_valid(arg):\nkargs = {}\nfor x in descriptor['to_one']:\nrelated_model_field_name = self.camel_case_to_snake_case(x['model_name'])\nif related_model_field_name in models:\nkargs[x['field']] = just_one(models[related_model_field_name])\nwithout_through = [x for x in descriptor['to_many'] if x['custom_through'] == False]\nfor x in without_through:\nrelated_model_field_name = self.camel_case_to_snake_case(x['model_name'])\nif related_model_field_name in models:\nkargs[x['field']] = get_list(models[related_model_field_name])\nmodels[model_field_name] = create_models(arg, f'{app_name}.{model_name}', **kargs)\nwith_through = [\nx for x in descriptor['to_many']\nif x['custom_through'] == True and not x['field'].endswith('_set')\n]\nfor x in with_through:\nrelated_model_field_name = self.camel_case_to_snake_case(x['model_name'])\nif related_model_field_name in models:\nfor item in get_list(models[related_model_field_name]):\nthrough_current = x['through_fields'][0]\nthrough_related = x['through_fields'][1]\nthrough_args = {through_current: models[model_field_name], through_related: item}\nx['handler'].through.objects.create(**through_args)\nreturn models\ndef link_deferred_model(models, descriptor, *args, **kwargs):\nmodel_field_name = self.camel_case_to_snake_case(descriptor['meta']['model_name'])\napp_name = descriptor['meta']['app_name']\nmodel_name = descriptor['meta']['model_name']\nif model_field_name in kwargs and f'{app_name}__{model_field_name}' in kwargs:\nraise Exception(f'Exists many apps with the same model name `{model_name}`, please use '\nf'`{app_name}__{model_field_name}` instead of `{model_field_name}`')\nif model_field_name in models:\nitems = models[model_field_name] if isinstance(models[model_field_name],\nlist) else [models[model_field_name]]\nfor m in items:\nfor x in descriptor['to_one']:\nrelated_model_field_name = self.camel_case_to_snake_case(x['model_name'])\nmodel_exists = related_model_field_name in models\nis_list = isinstance(models[model_field_name], list) if model_exists else False\nif model_exists and not is_list and not getattr(models[model_field_name], x['field']):\nsetattr(m, x['field'], just_one(models[related_model_field_name]))\nif model_exists and is_list:\nfor y in models[model_field_name]:\nif getattr(y, x['field']):\nsetattr(m, x['field'], just_one(models[related_model_field_name]))\nfor x in descriptor['to_many']:\nrelated_model_field_name = self.camel_case_to_snake_case(x['model_name'])\nif related_model_field_name in models and not getattr(\nmodels[model_field_name], x['field']):\nsetattr(m, x['field'], get_list(models[related_model_field_name]))\nsetattr(m, '__mixer__', None)\nm.save()\nreturn models\ndef wrapper(*args, **kwargs):\nmodels = {}\nfor generation_round in order:\nfor app_key in generation_round:\nfor descriptor_key in descriptors[app_key]:\ndescriptor = descriptors[app_key][descriptor_key]\nattr = self.camel_case_to_snake_case(descriptor['meta']['model_name'])\nmodels = manage_model(models, descriptor, *args, **kwargs)\nif app_key not in arguments:\narguments[app_key] = {}\narguments[attr] = ...\nelse:\narguments_banned.add(attr)\narguments[f'{app_key}__{attr}'] = ...\nfor generation_round in order:\nfor app_key in generation_round:\nfor descriptor_key in descriptors[app_key]:\ndescriptor = descriptors[app_key][descriptor_key]\nattr = self.camel_case_to_snake_case(descriptor['meta']['model_name'])\nmodels = link_deferred_model(models, descriptor, *args, **kwargs)\nif app_key not in arguments:\narguments[app_key] = {}\narguments[attr] = ...\nelse:\narguments_banned.add(attr)\narguments[f'{app_key}__{attr}'] = ...\nreturn AttrDict(**models)\nreturn wrapper\ndef create_v2(self, *args, **kwargs) -> dict[str, Model | list[Model]]:\n\"\"\"\n Unstable version of mixin that create all models, do not use this.\n \"\"\"\nmodels = self._get_models_handlers()(*args, **kwargs)\nreturn models\ndef create(self, *args, **kwargs) -> dict[str, Model | list[Model]]:\n\"\"\"\n Create one o many instances of models and return it like a dict of models.\n Usage:\n ```py\n # create three users\n self.bc.database.create(user=3)\n # create one user with a specific first name\n user = {'first_name': 'Lacey'}\n self.bc.database.create(user=user)\n # create two users with a specific first name and last name\n users = [\n {'first_name': 'Lacey', 'last_name': 'Sturm'},\n {'first_name': 'The', 'last_name': 'Warning'},\n ]\n self.bc.database.create(user=users)\n # create two users with the same first name\n user = {'first_name': 'Lacey'}\n self.bc.database.create(user=(2, user))\n # setting up manually the relationships\n cohort_user = {'cohort_id': 2}\n self.bc.database.create(cohort=2, cohort_user=cohort_user)\n ```\n It get the model name as snake case, you can pass a `bool`, `int`, `dict`, `tuple`, `list[dict]` or\n `list[tuple]`.\n Behavior for type of argument:\n - `bool`: if it is true generate a instance of a model.\n - `int`: generate a instance of a model n times, if `n` > 1 this is a list.\n - `dict`: generate a instance of a model, this pass to mixer.blend custom values to the model.\n - `tuple`: one element need to be a int and the other be a dict, generate a instance of a model n times,\n if `n` > 1 this is a list, this pass to mixer.blend custom values to the model.\n - `list[dict]`: generate a instance of a model n times, if `n` > 1 this is a list,\n this pass to mixer.blend custom values to the model.\n - `list[tuple]`: generate a instance of a model n times, if `n` > 1 this is a list for each element,\n this pass to mixer.blend custom values to the model.\n Keywords arguments deprecated:\n - models: this arguments is use to implement inheritance, receive as argument the output of other\n `self.bc.database.create()` execution.\n - authenticate: create a user and use `APITestCase.client.force_authenticate(user=models['user'])` to\n get credentials.\n \"\"\"\nreturn GenerateModelsMixin.generate_models(self._parent, _new_implementation=True, *args, **kwargs)\n@database_sync_to_async\ndef async_create(self, *args, **kwargs) -> dict[str, Model | list[Model]]:\n\"\"\"\n This is a wrapper for `Model.objects.count()`, get how many instances of this `Model` are saved.\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n \"\"\"\nreturn self.create(*args, **kwargs)\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.async_count","title":"async_count(path)
","text":"This is a wrapper for Model.objects.count()
, get how many instances of this Model
are saved.
Keywords arguments: - path(str
): path to a model, for example admissions.CohortUser
.
breathecode/tests/mixins/breathecode_mixin/database.py
@database_sync_to_async\ndef async_count(self, path: str) -> int:\n\"\"\"\n This is a wrapper for `Model.objects.count()`, get how many instances of this `Model` are saved.\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n \"\"\"\nreturn self.count(path)\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.async_create","title":"async_create(*args, **kwargs)
","text":"This is a wrapper for Model.objects.count()
, get how many instances of this Model
are saved.
Keywords arguments: - path(str
): path to a model, for example admissions.CohortUser
.
breathecode/tests/mixins/breathecode_mixin/database.py
@database_sync_to_async\ndef async_create(self, *args, **kwargs) -> dict[str, Model | list[Model]]:\n\"\"\"\n This is a wrapper for `Model.objects.count()`, get how many instances of this `Model` are saved.\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n \"\"\"\nreturn self.create(*args, **kwargs)\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.async_get","title":"async_get(path, pk, dict=True)
","text":"This is a wrapper for Model.objects.filter(pk=pk).first()
, get the values of model as dict
if dict=True
else get the Model
instance.
Keywords arguments: - path(str
): path to a model, for example admissions.CohortUser
. - pk(str | int
): primary key of model. - dict(bool
): if true return dict of values of model else return model instance.
breathecode/tests/mixins/breathecode_mixin/database.py
@database_sync_to_async\ndef async_get(self, path: str, pk: int | str, dict: bool = True) -> Model | dict[str, Any]:\n\"\"\"\n This is a wrapper for `Model.objects.filter(pk=pk).first()`, get the values of model as `dict` if\n `dict=True` else get the `Model` instance.\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n - pk(`str | int`): primary key of model.\n - dict(`bool`): if true return dict of values of model else return model instance.\n \"\"\"\nreturn self.get(path, pk, dict)\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.async_list_of","title":"async_list_of(path, dict=True)
","text":"This is a wrapper for Model.objects.filter()
, get a list of values of models as list[dict]
if dict=True
else get a list of Model
instances.
Keywords arguments: - path(str
): path to a model, for example admissions.CohortUser
. - dict(bool
): if true return dict of values of model else return model instance.
breathecode/tests/mixins/breathecode_mixin/database.py
@database_sync_to_async\ndef async_list_of(self, path: str, dict: bool = True) -> list[Model | dict[str, Any]]:\n\"\"\"\n This is a wrapper for `Model.objects.filter()`, get a list of values of models as `list[dict]` if\n `dict=True` else get a list of `Model` instances.\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n - dict(`bool`): if true return dict of values of model else return model instance.\n \"\"\"\nreturn self.list_of(path, dict)\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.count","title":"count(path)
","text":"This is a wrapper for Model.objects.count()
, get how many instances of this Model
are saved.
Usage:
self.bc.database.count('admissions.Cohort')\n
Keywords arguments: - path(str
): path to a model, for example admissions.CohortUser
.
breathecode/tests/mixins/breathecode_mixin/database.py
def count(self, path: str) -> int:\n\"\"\"\n This is a wrapper for `Model.objects.count()`, get how many instances of this `Model` are saved.\n Usage:\n ```py\n self.bc.database.count('admissions.Cohort')\n ```\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n \"\"\"\nmodel = Database.get_model(path)\nreturn model.objects.count()\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.create","title":"create(*args, **kwargs)
","text":"Create one o many instances of models and return it like a dict of models.
Usage:
# create three users\nself.bc.database.create(user=3)\n# create one user with a specific first name\nuser = {'first_name': 'Lacey'}\nself.bc.database.create(user=user)\n# create two users with a specific first name and last name\nusers = [\n{'first_name': 'Lacey', 'last_name': 'Sturm'},\n{'first_name': 'The', 'last_name': 'Warning'},\n]\nself.bc.database.create(user=users)\n# create two users with the same first name\nuser = {'first_name': 'Lacey'}\nself.bc.database.create(user=(2, user))\n# setting up manually the relationships\ncohort_user = {'cohort_id': 2}\nself.bc.database.create(cohort=2, cohort_user=cohort_user)\n
It get the model name as snake case, you can pass a bool
, int
, dict
, tuple
, list[dict]
or list[tuple]
.
Behavior for type of argument:
bool
: if it is true generate a instance of a model.int
: generate a instance of a model n times, if n
> 1 this is a list.dict
: generate a instance of a model, this pass to mixer.blend custom values to the model.tuple
: one element need to be a int and the other be a dict, generate a instance of a model n times, if n
> 1 this is a list, this pass to mixer.blend custom values to the model.list[dict]
: generate a instance of a model n times, if n
> 1 this is a list, this pass to mixer.blend custom values to the model.list[tuple]
: generate a instance of a model n times, if n
> 1 this is a list for each element, this pass to mixer.blend custom values to the model.Keywords arguments deprecated: - models: this arguments is use to implement inheritance, receive as argument the output of other self.bc.database.create()
execution. - authenticate: create a user and use APITestCase.client.force_authenticate(user=models['user'])
to get credentials.
breathecode/tests/mixins/breathecode_mixin/database.py
def create(self, *args, **kwargs) -> dict[str, Model | list[Model]]:\n\"\"\"\n Create one o many instances of models and return it like a dict of models.\n Usage:\n ```py\n # create three users\n self.bc.database.create(user=3)\n # create one user with a specific first name\n user = {'first_name': 'Lacey'}\n self.bc.database.create(user=user)\n # create two users with a specific first name and last name\n users = [\n {'first_name': 'Lacey', 'last_name': 'Sturm'},\n {'first_name': 'The', 'last_name': 'Warning'},\n ]\n self.bc.database.create(user=users)\n # create two users with the same first name\n user = {'first_name': 'Lacey'}\n self.bc.database.create(user=(2, user))\n # setting up manually the relationships\n cohort_user = {'cohort_id': 2}\n self.bc.database.create(cohort=2, cohort_user=cohort_user)\n ```\n It get the model name as snake case, you can pass a `bool`, `int`, `dict`, `tuple`, `list[dict]` or\n `list[tuple]`.\n Behavior for type of argument:\n - `bool`: if it is true generate a instance of a model.\n - `int`: generate a instance of a model n times, if `n` > 1 this is a list.\n - `dict`: generate a instance of a model, this pass to mixer.blend custom values to the model.\n - `tuple`: one element need to be a int and the other be a dict, generate a instance of a model n times,\n if `n` > 1 this is a list, this pass to mixer.blend custom values to the model.\n - `list[dict]`: generate a instance of a model n times, if `n` > 1 this is a list,\n this pass to mixer.blend custom values to the model.\n - `list[tuple]`: generate a instance of a model n times, if `n` > 1 this is a list for each element,\n this pass to mixer.blend custom values to the model.\n Keywords arguments deprecated:\n - models: this arguments is use to implement inheritance, receive as argument the output of other\n `self.bc.database.create()` execution.\n - authenticate: create a user and use `APITestCase.client.force_authenticate(user=models['user'])` to\n get credentials.\n \"\"\"\nreturn GenerateModelsMixin.generate_models(self._parent, _new_implementation=True, *args, **kwargs)\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.create_v2","title":"create_v2(*args, **kwargs)
","text":"Unstable version of mixin that create all models, do not use this.
Source code inbreathecode/tests/mixins/breathecode_mixin/database.py
def create_v2(self, *args, **kwargs) -> dict[str, Model | list[Model]]:\n\"\"\"\n Unstable version of mixin that create all models, do not use this.\n \"\"\"\nmodels = self._get_models_handlers()(*args, **kwargs)\nreturn models\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.delete","title":"delete(path, pk=None)
","text":"This is a wrapper for Model.objects.filter(pk=pk).delete()
, delete a element if pk
is provided else all the entries.
Usage:
# create 19110911 cohorts \ud83e\uddbe\nself.bc.database.create(cohort=19110911)\n# exists 19110911 cohorts \ud83e\uddbe\nself.assertEqual(self.bc.database.count('admissions.Cohort'), 19110911)\n# remove all the cohorts\nself.bc.database.delete(10)\n# exists 19110910 cohorts\nself.assertEqual(self.bc.database.count('admissions.Cohort'), 19110910)\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.delete--remove-all-the-cohorts","title":"remove all the cohorts","text":"self.bc.database.delete()
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.delete--exists-0-cohorts","title":"exists 0 cohorts","text":"self.assertEqual(self.bc.database.count('admissions.Cohort'), 0) ```
Keywords arguments: - path(str
): path to a model, for example admissions.CohortUser
. - pk(str | int
): primary key of model.
breathecode/tests/mixins/breathecode_mixin/database.py
def delete(self, path: str, pk: Optional[int | str] = None) -> tuple[int, dict[str, int]]:\n\"\"\"\n This is a wrapper for `Model.objects.filter(pk=pk).delete()`, delete a element if `pk` is provided else\n all the entries.\n Usage:\n ```py\n # create 19110911 cohorts \ud83e\uddbe\n self.bc.database.create(cohort=19110911)\n # exists 19110911 cohorts \ud83e\uddbe\n self.assertEqual(self.bc.database.count('admissions.Cohort'), 19110911)\n # remove all the cohorts\n self.bc.database.delete(10)\n # exists 19110910 cohorts\n self.assertEqual(self.bc.database.count('admissions.Cohort'), 19110910)\n ```\n # remove all the cohorts\n self.bc.database.delete()\n # exists 0 cohorts\n self.assertEqual(self.bc.database.count('admissions.Cohort'), 0)\n ```\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n - pk(`str | int`): primary key of model.\n \"\"\"\nlookups = {'pk': pk} if pk else {}\nmodel = Database.get_model(path)\nreturn model.objects.filter(**lookups).delete()\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.get","title":"get(path, pk, dict=True)
","text":"This is a wrapper for Model.objects.filter(pk=pk).first()
, get the values of model as dict
if dict=True
else get the Model
instance.
Usage:
# get the Cohort with the pk 1 as dict\nself.bc.database.get('admissions.Cohort', 1)\n# get the Cohort with the pk 1 as instance of model\nself.bc.database.get('admissions.Cohort', 1, dict=False)\n
Keywords arguments: - path(str
): path to a model, for example admissions.CohortUser
. - pk(str | int
): primary key of model. - dict(bool
): if true return dict of values of model else return model instance.
breathecode/tests/mixins/breathecode_mixin/database.py
def get(self, path: str, pk: int or str, dict: bool = True) -> Model | dict[str, Any]:\n\"\"\"\n This is a wrapper for `Model.objects.filter(pk=pk).first()`, get the values of model as `dict` if\n `dict=True` else get the `Model` instance.\n Usage:\n ```py\n # get the Cohort with the pk 1 as dict\n self.bc.database.get('admissions.Cohort', 1)\n # get the Cohort with the pk 1 as instance of model\n self.bc.database.get('admissions.Cohort', 1, dict=False)\n ```\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n - pk(`str | int`): primary key of model.\n - dict(`bool`): if true return dict of values of model else return model instance.\n \"\"\"\nmodel = Database.get_model(path)\nresult = model.objects.filter(pk=pk).first()\nif dict:\nresult = ModelsMixin.remove_dinamics_fields(self, result.__dict__.copy())\nreturn result\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.get_model","title":"get_model(path)
classmethod
","text":"Return the model matching the given app_label and model_name.
As a shortcut, app_label may be in the form ..
model_name is case-insensitive.
Raise LookupError if no application exists with this label, or no model exists with this name in the application. Raise ValueError if called with a single argument that doesn't contain exactly one dot.
Usage:
# class breathecode.admissions.models.Cohort\nCohort = self.bc.database.get_model('admissions.Cohort')\n
Keywords arguments: - path(str
): path to a model, for example admissions.CohortUser
.
breathecode/tests/mixins/breathecode_mixin/database.py
@classmethod\ndef get_model(cls, path: str) -> Model:\n\"\"\"\n Return the model matching the given app_label and model_name.\n As a shortcut, app_label may be in the form <app_label>.<model_name>.\n model_name is case-insensitive.\n Raise LookupError if no application exists with this label, or no\n model exists with this name in the application. Raise ValueError if\n called with a single argument that doesn't contain exactly one dot.\n Usage:\n ```py\n # class breathecode.admissions.models.Cohort\n Cohort = self.bc.database.get_model('admissions.Cohort')\n ```\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n \"\"\"\nif path in cls._cache:\nreturn cls._cache[path]\napp_label, model_name = path.split('.')\ncls._cache[path] = apps.get_model(app_label, model_name)\nreturn cls._cache[path]\n
"},{"location":"testing/mixins/bc-database/#breathecode.tests.mixins.breathecode_mixin.database.Database.list_of","title":"list_of(path, dict=True)
","text":"This is a wrapper for Model.objects.filter()
, get a list of values of models as list[dict]
if dict=True
else get a list of Model
instances.
Usage:
# get all the Cohort as list of dict\nself.bc.database.get('admissions.Cohort')\n# get all the Cohort as list of instances of model\nself.bc.database.get('admissions.Cohort', dict=False)\n
Keywords arguments: - path(str
): path to a model, for example admissions.CohortUser
. - dict(bool
): if true return dict of values of model else return model instance.
breathecode/tests/mixins/breathecode_mixin/database.py
def list_of(self, path: str, dict: bool = True) -> list[Model | dict[str, Any]]:\n\"\"\"\n This is a wrapper for `Model.objects.filter()`, get a list of values of models as `list[dict]` if\n `dict=True` else get a list of `Model` instances.\n Usage:\n ```py\n # get all the Cohort as list of dict\n self.bc.database.get('admissions.Cohort')\n # get all the Cohort as list of instances of model\n self.bc.database.get('admissions.Cohort', dict=False)\n ```\n Keywords arguments:\n - path(`str`): path to a model, for example `admissions.CohortUser`.\n - dict(`bool`): if true return dict of values of model else return model instance.\n \"\"\"\nmodel = Database.get_model(path)\nresult = model.objects.filter()\nif dict:\nresult = [ModelsMixin.remove_dinamics_fields(self, data.__dict__.copy()) for data in result]\nreturn result\n
"},{"location":"testing/mixins/bc-datetime/","title":"bc.datetime","text":""},{"location":"testing/mixins/bc-datetime/#breathecode.tests.mixins.breathecode_mixin.datetime.Datetime","title":"Datetime
","text":"Mixin with the purpose of cover all the related with datetime
Source code inbreathecode/tests/mixins/breathecode_mixin/datetime.py
class Datetime:\n\"\"\"Mixin with the purpose of cover all the related with datetime\"\"\"\nto_iso_string = DatetimeMixin.datetime_to_iso\nfrom_iso_string = DatetimeMixin.iso_to_datetime\nnow = DatetimeMixin.datetime_now\n_parent: APITestCase\n_bc: interfaces.BreathecodeInterface\ndef __init__(self, parent, bc: interfaces.BreathecodeInterface) -> None:\nself._parent = parent\nself._bc = bc\ndef from_timedelta(self, delta=timedelta(seconds=0)) -> str:\n\"\"\"\n Transform from timedelta to the totals seconds in str.\n Usage:\n ```py\n from datetime import timedelta\n delta = timedelta(seconds=777)\n self.bc.datetime.from_timedelta(delta) # equals to '777.0'\n ```\n \"\"\"\nreturn str(delta.total_seconds())\ndef to_datetime_integer(self, timezone: str, date: datetime) -> int:\n\"\"\"\n Transform datetime to datetime integer.\n Usage:\n ```py\n utc_now = timezone.now()\n # date\n date = datetime.datetime(2022, 3, 21, 2, 51, 55, 068)\n # equals to 202203210751\n self.bc.datetime.to_datetime_integer('america/new_york', date)\n ```\n \"\"\"\nreturn DatetimeInteger.from_datetime(timezone, date)\n
"},{"location":"testing/mixins/bc-datetime/#breathecode.tests.mixins.breathecode_mixin.datetime.Datetime.from_timedelta","title":"from_timedelta(delta=timedelta(seconds=0))
","text":"Transform from timedelta to the totals seconds in str.
Usage:
from datetime import timedelta\ndelta = timedelta(seconds=777)\nself.bc.datetime.from_timedelta(delta) # equals to '777.0'\n
Source code in breathecode/tests/mixins/breathecode_mixin/datetime.py
def from_timedelta(self, delta=timedelta(seconds=0)) -> str:\n\"\"\"\n Transform from timedelta to the totals seconds in str.\n Usage:\n ```py\n from datetime import timedelta\n delta = timedelta(seconds=777)\n self.bc.datetime.from_timedelta(delta) # equals to '777.0'\n ```\n \"\"\"\nreturn str(delta.total_seconds())\n
"},{"location":"testing/mixins/bc-datetime/#breathecode.tests.mixins.breathecode_mixin.datetime.Datetime.to_datetime_integer","title":"to_datetime_integer(timezone, date)
","text":"Transform datetime to datetime integer.
Usage:
utc_now = timezone.now()\n# date\ndate = datetime.datetime(2022, 3, 21, 2, 51, 55, 068)\n# equals to 202203210751\nself.bc.datetime.to_datetime_integer('america/new_york', date)\n
Source code in breathecode/tests/mixins/breathecode_mixin/datetime.py
def to_datetime_integer(self, timezone: str, date: datetime) -> int:\n\"\"\"\n Transform datetime to datetime integer.\n Usage:\n ```py\n utc_now = timezone.now()\n # date\n date = datetime.datetime(2022, 3, 21, 2, 51, 55, 068)\n # equals to 202203210751\n self.bc.datetime.to_datetime_integer('america/new_york', date)\n ```\n \"\"\"\nreturn DatetimeInteger.from_datetime(timezone, date)\n
"},{"location":"testing/mixins/bc-fake/","title":"bc.fake","text":"Represents a instance of Faker you can learn about it in their webside
"},{"location":"testing/mixins/bc-format/","title":"bc.format","text":""},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format","title":"Format
","text":"Mixin with the purpose of cover all the related with format or parse something
Source code inbreathecode/tests/mixins/breathecode_mixin/format.py
class Format:\n\"\"\"Mixin with the purpose of cover all the related with format or parse something\"\"\"\n_parent: APITestCase\n_bc: interfaces.BreathecodeInterface\nENCODE = ENCODE\ndef __init__(self, parent, bc: interfaces.BreathecodeInterface) -> None:\nself._parent = parent\nself._bc = bc\ndef call(self, *args: Any, **kwargs: Any) -> str:\n\"\"\"\n Wraps a call into it and return its args and kwargs.\n example:\n ```py\n args, kwargs = self.bc.format.call(2, 3, 4, a=1, b=2, c=3)\n assert args == (2, 3, 4)\n assert kwargs == {'a': 1, 'b': 2, 'c': 3}\n ```\n \"\"\"\nreturn args, kwargs\ndef querystring(self, query: dict) -> str:\n\"\"\"\n Build a querystring from a given dict.\n \"\"\"\nreturn urllib.parse.urlencode(query)\ndef queryset(self, query: dict) -> str:\n\"\"\"\n Build a QuerySet from a given dict.\n \"\"\"\nreturn Q(**query)\n# remove lang from args\ndef lookup(self, lang: str, overwrite: dict = dict(), **kwargs: dict | tuple) -> dict[str, Any]:\n\"\"\"\n Generate from lookups the values in test side to be used in querystring.\n example:\n ```py\n query = self.bc.format.lookup(\n 'en',\n strings={\n 'exact': [\n 'remote_meeting_url',\n ],\n },\n bools={\n 'is_null': ['ended_at'],\n },\n datetimes={\n 'gte': ['starting_at'],\n 'lte': ['ending_at'],\n },\n slugs=[\n 'cohort_time_slot__cohort',\n 'cohort_time_slot__cohort__academy',\n 'cohort_time_slot__cohort__syllabus_version__syllabus',\n ],\n overwrite={\n 'cohort': 'cohort_time_slot__cohort',\n 'academy': 'cohort_time_slot__cohort__academy',\n 'syllabus': 'cohort_time_slot__cohort__syllabus_version__syllabus',\n 'start': 'starting_at',\n 'end': 'ending_at',\n 'upcoming': 'ended_at',\n },\n )\n url = reverse_lazy('events:me_event_liveclass') + '?' + self.bc.format.querystring(query)\n # this test avoid to pass a invalid param to ORM\n response = self.client.get(url)\n ```\n \"\"\"\nresult = {}\n# foreign\nids = kwargs.get('ids', tuple())\nslugs = kwargs.get('slugs', tuple())\n# fields\nints = kwargs.get('ints', dict())\nstrings = kwargs.get('strings', dict())\ndatetimes = kwargs.get('datetimes', dict())\nbools = kwargs.get('bools', dict())\n# opts\ncustom_fields = kwargs.get('custom_fields', dict())\n# serialize foreign\nids = tuple(ids)\nslugs = tuple(slugs)\noverwrite = dict([(v, k) for k, v in overwrite.items()])\n# foreign\nfor field in ids:\nif field == '':\nresult['id'] = field.integer('exact')\ncontinue\nname = overwrite.get(field, field)\nresult[name] = Field.id('')\nfor field in slugs:\nif field == '':\nresult['id'] = Field.integer('exact')\nresult['slug'] = Field.string('exact')\ncontinue\nname = overwrite.get(field, field)\nresult[name] = Field.slug('')\n# fields\nfor mode in ints:\nfor field in ints[mode]:\nname = overwrite.get(field, field)\nresult[name] = Field.int(mode)\nfor mode in strings:\nfor field in strings[mode]:\nname = overwrite.get(field, field)\nresult[name] = Field.string(mode)\nfor mode in datetimes:\nfor field in datetimes[mode]:\nname = overwrite.get(field, field)\nresult[name] = Field.datetime(mode)\nfor mode in bools:\nfor field in bools[mode]:\nname = overwrite.get(field, field)\nresult[name] = Field.bool(mode)\n# custom fields\nfor field in custom_fields:\nname = overwrite.get(field, field)\nresult[name] = custom_fields[field]()\nreturn result\ndef table(self, arg: QuerySet) -> dict[str, Any] | list[dict[str, Any]]:\n\"\"\"\n Convert a QuerySet in a list.\n Usage:\n ```py\n model = self.bc.database.create(user=1, group=1)\n self.bc.format.model(model.user.groups.all()) # = [{...}]\n ```\n \"\"\"\nreturn [ModelsMixin.remove_dinamics_fields(self, data.__dict__.copy()) for data in arg]\ndef to_dict(self, arg: Any) -> dict[str, Any] | list[dict[str, Any]]:\n\"\"\"\n Parse the object to a `dict` or `list[dict]`.\n Usage:\n ```py\n # setup the database, model.user is instance of dict and model.cohort\n # is instance list of dicts\n model = self.bc.database.create(user=1, cohort=2)\n # Parsing one model to a dict\n self.bc.format.to_dict(model.user) # = {...}\n # Parsing many models to a list of dict (infered from the type of\n # argument)\n self.bc.format.to_dict(model.cohort) # = [{...}, {...}]\n ```\n \"\"\"\nif isinstance(arg, list) or isinstance(arg, QuerySet):\nreturn [self._one_to_dict(x) for x in arg]\nreturn self._one_to_dict(arg)\ndef to_decimal_string(self, decimal: int | float) -> str:\n\"\"\"\n Parse a number to the django representation of a decimal.\n Usage:\n ```py\n self.bc.format.to_decimal(1) # returns '1.000000000000000'\n ```\n \"\"\"\nreturn '%.15f' % round(decimal, 15)\ndef _one_to_dict(self, arg) -> dict[str, Any]:\n\"\"\"Parse the object to a `dict`\"\"\"\nif isinstance(arg, Model):\nreturn ModelsMixin.remove_dinamics_fields(None, vars(arg))\nif isinstance(arg, dict):\nreturn arg\nraise NotImplementedError(f'{arg.__name__} is not implemented yet')\ndef describe_models(self, models: dict[str, Model]) -> str:\n\"\"\"\n Describe the models.\n Usage:\n ```py\n # setup the database\n model = self.bc.database.create(user=1, cohort=1)\n # print the docstring to the corresponding test\n self.bc.format.describe_models(model)\n ```\n \"\"\"\ntitle_spaces = ' ' * 8\nmodel_spaces = ' ' * 10\nresult = {}\nfor key in models:\nmodel = models[key]\nif isinstance(model, list):\nfor v in model:\nname, obj = self._describe_model(v)\nresult[name] = obj\nelse:\nname, obj = self._describe_model(model)\nresult[name] = obj\nprint(title_spaces + 'Descriptions of models are being generated:')\nfor line in yaml.dump(result).split('\\n'):\nif not line.startswith(' '):\nprint()\nprint(model_spaces + line)\n# This make sure the element are being printed and prevent `describe_models` are pushed to dev branch\nassert False\n#TODO: this method is buggy in the line `if not hasattr(model, key)`\ndef _describe_model(self, model: Model):\npk_name = self._get_pk_name(model)\nattrs = dir(model)\nresult = {}\nfor key in attrs:\nif key.startswith('_'):\ncontinue\nif key == 'DoesNotExist':\ncontinue\nif key == 'MultipleObjectsReturned':\ncontinue\nif key.startswith('get_next_'):\ncontinue\nif key.startswith('get_previous_'):\ncontinue\nif key.endswith('_set'):\ncontinue\nif not hasattr(model, key):\ncontinue\nattr = getattr(model, key)\nif attr.__class__.__name__ == 'method':\ncontinue\nif isinstance(attr, Model):\nresult[key] = f'{attr.__class__.__name__}({self._get_pk_name(attr)}={self._repr_pk(attr.pk)})'\nelif attr.__class__.__name__ == 'ManyRelatedManager':\ninstances = [\nf'{attr.model.__name__}({self._get_pk_name(x)}={self._repr_pk(x.pk)})'\nfor x in attr.get_queryset()\n]\nresult[key] = instances\nreturn (f'{model.__class__.__name__}({pk_name}={self._repr_pk(model.pk)})', result)\ndef _repr_pk(self, pk: str | int) -> int | str:\nif isinstance(pk, int):\nreturn pk\nreturn f'\"{pk}\"'\ndef _get_pk_name(self, model: Model):\nfrom django.db.models.fields import Field, SlugField\nattrs = [\nx for x in dir(model)\nif hasattr(model.__class__, x) and (isinstance(getattr(model.__class__, x), SlugField)\nor isinstance(getattr(model.__class__, x), SlugField))\nand getattr(model.__class__, x).primary_key\n]\nfor key in dir(model):\nif (hasattr(model.__class__, key) and hasattr(getattr(model.__class__, key), 'field')\nand getattr(model.__class__, key).field.primary_key):\nreturn key\nreturn 'pk'\ndef from_base64(self, hash: str | bytes) -> str:\n\"\"\"\n Transform a base64 hash to string.\n \"\"\"\nif isinstance(hash, str):\nhash = hash.encode()\nreturn base64.b64decode(hash).decode(ENCODE)\ndef to_base64(self, string: str | bytes) -> str:\n\"\"\"\n Transform a base64 hash to string.\n \"\"\"\nif isinstance(string, str):\nstring = string.encode()\nreturn base64.b64encode(string).decode(ENCODE)\ndef to_querystring(self, params: dict) -> str:\n\"\"\"\n Transform dict to querystring\n \"\"\"\nreturn urllib.parse.urlencode(params)\ndef from_bytes(self, s: bytes, encode: str = ENCODE) -> str:\n\"\"\"\n Transform bytes to a string.\n \"\"\"\nreturn s.decode(encode)\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.call","title":"call(*args, **kwargs)
","text":"Wraps a call into it and return its args and kwargs.
example:
args, kwargs = self.bc.format.call(2, 3, 4, a=1, b=2, c=3)\nassert args == (2, 3, 4)\nassert kwargs == {'a': 1, 'b': 2, 'c': 3}\n
Source code in breathecode/tests/mixins/breathecode_mixin/format.py
def call(self, *args: Any, **kwargs: Any) -> str:\n\"\"\"\n Wraps a call into it and return its args and kwargs.\n example:\n ```py\n args, kwargs = self.bc.format.call(2, 3, 4, a=1, b=2, c=3)\n assert args == (2, 3, 4)\n assert kwargs == {'a': 1, 'b': 2, 'c': 3}\n ```\n \"\"\"\nreturn args, kwargs\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.describe_models","title":"describe_models(models)
","text":"Describe the models.
Usage:
# setup the database\nmodel = self.bc.database.create(user=1, cohort=1)\n# print the docstring to the corresponding test\nself.bc.format.describe_models(model)\n
Source code in breathecode/tests/mixins/breathecode_mixin/format.py
def describe_models(self, models: dict[str, Model]) -> str:\n\"\"\"\n Describe the models.\n Usage:\n ```py\n # setup the database\n model = self.bc.database.create(user=1, cohort=1)\n # print the docstring to the corresponding test\n self.bc.format.describe_models(model)\n ```\n \"\"\"\ntitle_spaces = ' ' * 8\nmodel_spaces = ' ' * 10\nresult = {}\nfor key in models:\nmodel = models[key]\nif isinstance(model, list):\nfor v in model:\nname, obj = self._describe_model(v)\nresult[name] = obj\nelse:\nname, obj = self._describe_model(model)\nresult[name] = obj\nprint(title_spaces + 'Descriptions of models are being generated:')\nfor line in yaml.dump(result).split('\\n'):\nif not line.startswith(' '):\nprint()\nprint(model_spaces + line)\n# This make sure the element are being printed and prevent `describe_models` are pushed to dev branch\nassert False\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.from_base64","title":"from_base64(hash)
","text":"Transform a base64 hash to string.
Source code inbreathecode/tests/mixins/breathecode_mixin/format.py
def from_base64(self, hash: str | bytes) -> str:\n\"\"\"\n Transform a base64 hash to string.\n \"\"\"\nif isinstance(hash, str):\nhash = hash.encode()\nreturn base64.b64decode(hash).decode(ENCODE)\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.from_bytes","title":"from_bytes(s, encode=ENCODE)
","text":"Transform bytes to a string.
Source code inbreathecode/tests/mixins/breathecode_mixin/format.py
def from_bytes(self, s: bytes, encode: str = ENCODE) -> str:\n\"\"\"\n Transform bytes to a string.\n \"\"\"\nreturn s.decode(encode)\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.lookup","title":"lookup(lang, overwrite=dict(), **kwargs)
","text":"Generate from lookups the values in test side to be used in querystring.
example:
query = self.bc.format.lookup(\n'en',\nstrings={\n'exact': [\n'remote_meeting_url',\n],\n},\nbools={\n'is_null': ['ended_at'],\n},\ndatetimes={\n'gte': ['starting_at'],\n'lte': ['ending_at'],\n},\nslugs=[\n'cohort_time_slot__cohort',\n'cohort_time_slot__cohort__academy',\n'cohort_time_slot__cohort__syllabus_version__syllabus',\n],\noverwrite={\n'cohort': 'cohort_time_slot__cohort',\n'academy': 'cohort_time_slot__cohort__academy',\n'syllabus': 'cohort_time_slot__cohort__syllabus_version__syllabus',\n'start': 'starting_at',\n'end': 'ending_at',\n'upcoming': 'ended_at',\n},\n)\nurl = reverse_lazy('events:me_event_liveclass') + '?' + self.bc.format.querystring(query)\n# this test avoid to pass a invalid param to ORM\nresponse = self.client.get(url)\n
Source code in breathecode/tests/mixins/breathecode_mixin/format.py
def lookup(self, lang: str, overwrite: dict = dict(), **kwargs: dict | tuple) -> dict[str, Any]:\n\"\"\"\n Generate from lookups the values in test side to be used in querystring.\n example:\n ```py\n query = self.bc.format.lookup(\n 'en',\n strings={\n 'exact': [\n 'remote_meeting_url',\n ],\n },\n bools={\n 'is_null': ['ended_at'],\n },\n datetimes={\n 'gte': ['starting_at'],\n 'lte': ['ending_at'],\n },\n slugs=[\n 'cohort_time_slot__cohort',\n 'cohort_time_slot__cohort__academy',\n 'cohort_time_slot__cohort__syllabus_version__syllabus',\n ],\n overwrite={\n 'cohort': 'cohort_time_slot__cohort',\n 'academy': 'cohort_time_slot__cohort__academy',\n 'syllabus': 'cohort_time_slot__cohort__syllabus_version__syllabus',\n 'start': 'starting_at',\n 'end': 'ending_at',\n 'upcoming': 'ended_at',\n },\n )\n url = reverse_lazy('events:me_event_liveclass') + '?' + self.bc.format.querystring(query)\n # this test avoid to pass a invalid param to ORM\n response = self.client.get(url)\n ```\n \"\"\"\nresult = {}\n# foreign\nids = kwargs.get('ids', tuple())\nslugs = kwargs.get('slugs', tuple())\n# fields\nints = kwargs.get('ints', dict())\nstrings = kwargs.get('strings', dict())\ndatetimes = kwargs.get('datetimes', dict())\nbools = kwargs.get('bools', dict())\n# opts\ncustom_fields = kwargs.get('custom_fields', dict())\n# serialize foreign\nids = tuple(ids)\nslugs = tuple(slugs)\noverwrite = dict([(v, k) for k, v in overwrite.items()])\n# foreign\nfor field in ids:\nif field == '':\nresult['id'] = field.integer('exact')\ncontinue\nname = overwrite.get(field, field)\nresult[name] = Field.id('')\nfor field in slugs:\nif field == '':\nresult['id'] = Field.integer('exact')\nresult['slug'] = Field.string('exact')\ncontinue\nname = overwrite.get(field, field)\nresult[name] = Field.slug('')\n# fields\nfor mode in ints:\nfor field in ints[mode]:\nname = overwrite.get(field, field)\nresult[name] = Field.int(mode)\nfor mode in strings:\nfor field in strings[mode]:\nname = overwrite.get(field, field)\nresult[name] = Field.string(mode)\nfor mode in datetimes:\nfor field in datetimes[mode]:\nname = overwrite.get(field, field)\nresult[name] = Field.datetime(mode)\nfor mode in bools:\nfor field in bools[mode]:\nname = overwrite.get(field, field)\nresult[name] = Field.bool(mode)\n# custom fields\nfor field in custom_fields:\nname = overwrite.get(field, field)\nresult[name] = custom_fields[field]()\nreturn result\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.queryset","title":"queryset(query)
","text":"Build a QuerySet from a given dict.
Source code inbreathecode/tests/mixins/breathecode_mixin/format.py
def queryset(self, query: dict) -> str:\n\"\"\"\n Build a QuerySet from a given dict.\n \"\"\"\nreturn Q(**query)\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.querystring","title":"querystring(query)
","text":"Build a querystring from a given dict.
Source code inbreathecode/tests/mixins/breathecode_mixin/format.py
def querystring(self, query: dict) -> str:\n\"\"\"\n Build a querystring from a given dict.\n \"\"\"\nreturn urllib.parse.urlencode(query)\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.table","title":"table(arg)
","text":"Convert a QuerySet in a list.
Usage:
model = self.bc.database.create(user=1, group=1)\nself.bc.format.model(model.user.groups.all()) # = [{...}]\n
Source code in breathecode/tests/mixins/breathecode_mixin/format.py
def table(self, arg: QuerySet) -> dict[str, Any] | list[dict[str, Any]]:\n\"\"\"\n Convert a QuerySet in a list.\n Usage:\n ```py\n model = self.bc.database.create(user=1, group=1)\n self.bc.format.model(model.user.groups.all()) # = [{...}]\n ```\n \"\"\"\nreturn [ModelsMixin.remove_dinamics_fields(self, data.__dict__.copy()) for data in arg]\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.to_base64","title":"to_base64(string)
","text":"Transform a base64 hash to string.
Source code inbreathecode/tests/mixins/breathecode_mixin/format.py
def to_base64(self, string: str | bytes) -> str:\n\"\"\"\n Transform a base64 hash to string.\n \"\"\"\nif isinstance(string, str):\nstring = string.encode()\nreturn base64.b64encode(string).decode(ENCODE)\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.to_decimal_string","title":"to_decimal_string(decimal)
","text":"Parse a number to the django representation of a decimal.
Usage:
self.bc.format.to_decimal(1) # returns '1.000000000000000'\n
Source code in breathecode/tests/mixins/breathecode_mixin/format.py
def to_decimal_string(self, decimal: int | float) -> str:\n\"\"\"\n Parse a number to the django representation of a decimal.\n Usage:\n ```py\n self.bc.format.to_decimal(1) # returns '1.000000000000000'\n ```\n \"\"\"\nreturn '%.15f' % round(decimal, 15)\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.to_dict","title":"to_dict(arg)
","text":"Parse the object to a dict
or list[dict]
.
Usage:
# setup the database, model.user is instance of dict and model.cohort\n# is instance list of dicts\nmodel = self.bc.database.create(user=1, cohort=2)\n# Parsing one model to a dict\nself.bc.format.to_dict(model.user) # = {...}\n# Parsing many models to a list of dict (infered from the type of\n# argument)\nself.bc.format.to_dict(model.cohort) # = [{...}, {...}]\n
Source code in breathecode/tests/mixins/breathecode_mixin/format.py
def to_dict(self, arg: Any) -> dict[str, Any] | list[dict[str, Any]]:\n\"\"\"\n Parse the object to a `dict` or `list[dict]`.\n Usage:\n ```py\n # setup the database, model.user is instance of dict and model.cohort\n # is instance list of dicts\n model = self.bc.database.create(user=1, cohort=2)\n # Parsing one model to a dict\n self.bc.format.to_dict(model.user) # = {...}\n # Parsing many models to a list of dict (infered from the type of\n # argument)\n self.bc.format.to_dict(model.cohort) # = [{...}, {...}]\n ```\n \"\"\"\nif isinstance(arg, list) or isinstance(arg, QuerySet):\nreturn [self._one_to_dict(x) for x in arg]\nreturn self._one_to_dict(arg)\n
"},{"location":"testing/mixins/bc-format/#breathecode.tests.mixins.breathecode_mixin.format.Format.to_querystring","title":"to_querystring(params)
","text":"Transform dict to querystring
Source code inbreathecode/tests/mixins/breathecode_mixin/format.py
def to_querystring(self, params: dict) -> str:\n\"\"\"\n Transform dict to querystring\n \"\"\"\nreturn urllib.parse.urlencode(params)\n
"},{"location":"testing/mixins/bc-random/","title":"bc.random","text":""},{"location":"testing/mixins/bc-random/#breathecode.tests.mixins.breathecode_mixin.random.Random","title":"Random
","text":"Mixin with the purpose of cover all the related with the custom asserts
Source code inbreathecode/tests/mixins/breathecode_mixin/random.py
class Random:\n\"\"\"Mixin with the purpose of cover all the related with the custom asserts\"\"\"\n_parent: APITestCase\n_bc: interfaces.BreathecodeInterface\ndef __init__(self, parent, bc: interfaces.BreathecodeInterface) -> None:\nself._parent = parent\nself._bc = bc\ndef image(self, width: int = 10, height: int = 10, ext='png') -> tuple[TextIOWrapper, str]:\n\"\"\"\n Generate a random image.\n Usage:\n ```py\n # generate a random image with width of 20px and height of 10px\n file, filename = self.bc.random.image(20, 10)\n ```\n \"\"\"\nsize = (width, height)\nfilename = fake.slug() + f'.{ext}'\nimage = Image.new('RGB', size)\narr = np.random.randint(low=0, high=255, size=(size[1], size[0]))\nimage = Image.fromarray(arr.astype('uint8'))\nimage.save(filename, IMAGE_TYPES[ext])\nfile = open(filename, 'rb')\nself._bc.garbage_collector.register_image(file)\nreturn file, filename\ndef file(self) -> tuple[TextIOWrapper, str]:\n\"\"\"\n Generate a random file.\n Usage:\n ```py\n # generate a random file\n file, filename = self.bc.random.file()\n ```\n \"\"\"\next = self.string(lower=True, size=2)\nfile = tempfile.NamedTemporaryFile(suffix=f'.{ext}', delete=False)\nfile.write(os.urandom(1024))\nself._bc.garbage_collector.register_file(file)\nreturn file, file.name\ndef string(self, lower=False, upper=False, symbol=False, number=False, size=0) -> str:\nchars = ''\nif lower:\nchars = chars + string.ascii_lowercase\nif upper:\nchars = chars + string.ascii_uppercase\nif symbol:\nchars = chars + string.punctuation\nif number:\nchars = chars + string.digits\nreturn ''.join(random.choices(chars, k=size))\n
"},{"location":"testing/mixins/bc-random/#breathecode.tests.mixins.breathecode_mixin.random.Random.file","title":"file()
","text":"Generate a random file.
Usage:
# generate a random file\nfile, filename = self.bc.random.file()\n
Source code in breathecode/tests/mixins/breathecode_mixin/random.py
def file(self) -> tuple[TextIOWrapper, str]:\n\"\"\"\n Generate a random file.\n Usage:\n ```py\n # generate a random file\n file, filename = self.bc.random.file()\n ```\n \"\"\"\next = self.string(lower=True, size=2)\nfile = tempfile.NamedTemporaryFile(suffix=f'.{ext}', delete=False)\nfile.write(os.urandom(1024))\nself._bc.garbage_collector.register_file(file)\nreturn file, file.name\n
"},{"location":"testing/mixins/bc-random/#breathecode.tests.mixins.breathecode_mixin.random.Random.image","title":"image(width=10, height=10, ext='png')
","text":"Generate a random image.
Usage:
# generate a random image with width of 20px and height of 10px\nfile, filename = self.bc.random.image(20, 10)\n
Source code in breathecode/tests/mixins/breathecode_mixin/random.py
def image(self, width: int = 10, height: int = 10, ext='png') -> tuple[TextIOWrapper, str]:\n\"\"\"\n Generate a random image.\n Usage:\n ```py\n # generate a random image with width of 20px and height of 10px\n file, filename = self.bc.random.image(20, 10)\n ```\n \"\"\"\nsize = (width, height)\nfilename = fake.slug() + f'.{ext}'\nimage = Image.new('RGB', size)\narr = np.random.randint(low=0, high=255, size=(size[1], size[0]))\nimage = Image.fromarray(arr.astype('uint8'))\nimage.save(filename, IMAGE_TYPES[ext])\nfile = open(filename, 'rb')\nself._bc.garbage_collector.register_image(file)\nreturn file, filename\n
"},{"location":"testing/mixins/bc-request/","title":"bc.request","text":""},{"location":"testing/mixins/bc-request/#breathecode.tests.mixins.breathecode_mixin.request.Request","title":"Request
","text":"Mixin with the purpose of cover all the related with the request
Source code inbreathecode/tests/mixins/breathecode_mixin/request.py
class Request:\n\"\"\"Mixin with the purpose of cover all the related with the request\"\"\"\n_parent: APITestCase\ndef __init__(self, parent, bc) -> None:\nself._parent = parent\nself._bc = bc\ndef set_headers(self, **kargs: str) -> None:\n\"\"\"\n Set headers.\n ```py\n # It set the headers with:\n # Academy: 1\n # ThingOfImportance: potato\n self.bc.request.set_headers(academy=1, thing_of_importance='potato')\n ```\n \"\"\"\nheaders = {}\nitems = [\nindex for index in kargs\nif kargs[index] and (isinstance(kargs[index], str) or isinstance(kargs[index], int))\n]\nfor index in items:\nheaders[f'HTTP_{index.upper()}'] = str(kargs[index])\nself._parent.client.credentials(**headers)\ndef authenticate(self, user) -> None:\n\"\"\"\n Forces authentication in a request inside a APITestCase.\n Usage:\n ```py\n # setup the database\n model = self.bc.database.create(user=1)\n # that setup the request to use the credential of user passed\n self.bc.request.authenticate(model.user)\n ```\n Keywords arguments:\n - user: a instance of user model `breathecode.authenticate.models.User`\n \"\"\"\nself._parent.client.force_authenticate(user=user)\ndef manual_authentication(self, user) -> None:\n\"\"\"\n Generate a manual authentication using a token, this method is more slower than `authenticate`.\n ```py\n # setup the database\n model = self.bc.database.create(user=1)\n # that setup the request to use the credential with tokens of user passed\n self.bc.request.manual_authentication(model.user)\n ```\n Keywords arguments:\n - user: a instance of user model `breathecode.authenticate.models.User`.\n \"\"\"\nfrom breathecode.authenticate.models import Token\ntoken = Token.objects.create(user=user)\nself._parent.client.credentials(HTTP_AUTHORIZATION=f'Token {token.key}')\n
"},{"location":"testing/mixins/bc-request/#breathecode.tests.mixins.breathecode_mixin.request.Request.authenticate","title":"authenticate(user)
","text":"Forces authentication in a request inside a APITestCase.
Usage:
# setup the database\nmodel = self.bc.database.create(user=1)\n# that setup the request to use the credential of user passed\nself.bc.request.authenticate(model.user)\n
Keywords arguments:
breathecode.authenticate.models.User
breathecode/tests/mixins/breathecode_mixin/request.py
def authenticate(self, user) -> None:\n\"\"\"\n Forces authentication in a request inside a APITestCase.\n Usage:\n ```py\n # setup the database\n model = self.bc.database.create(user=1)\n # that setup the request to use the credential of user passed\n self.bc.request.authenticate(model.user)\n ```\n Keywords arguments:\n - user: a instance of user model `breathecode.authenticate.models.User`\n \"\"\"\nself._parent.client.force_authenticate(user=user)\n
"},{"location":"testing/mixins/bc-request/#breathecode.tests.mixins.breathecode_mixin.request.Request.manual_authentication","title":"manual_authentication(user)
","text":"Generate a manual authentication using a token, this method is more slower than authenticate
.
# setup the database\nmodel = self.bc.database.create(user=1)\n# that setup the request to use the credential with tokens of user passed\nself.bc.request.manual_authentication(model.user)\n
Keywords arguments:
breathecode.authenticate.models.User
.breathecode/tests/mixins/breathecode_mixin/request.py
def manual_authentication(self, user) -> None:\n\"\"\"\n Generate a manual authentication using a token, this method is more slower than `authenticate`.\n ```py\n # setup the database\n model = self.bc.database.create(user=1)\n # that setup the request to use the credential with tokens of user passed\n self.bc.request.manual_authentication(model.user)\n ```\n Keywords arguments:\n - user: a instance of user model `breathecode.authenticate.models.User`.\n \"\"\"\nfrom breathecode.authenticate.models import Token\ntoken = Token.objects.create(user=user)\nself._parent.client.credentials(HTTP_AUTHORIZATION=f'Token {token.key}')\n
"},{"location":"testing/mixins/bc-request/#breathecode.tests.mixins.breathecode_mixin.request.Request.set_headers","title":"set_headers(**kargs)
","text":"Set headers.
# It set the headers with:\n# Academy: 1\n# ThingOfImportance: potato\nself.bc.request.set_headers(academy=1, thing_of_importance='potato')\n
Source code in breathecode/tests/mixins/breathecode_mixin/request.py
def set_headers(self, **kargs: str) -> None:\n\"\"\"\n Set headers.\n ```py\n # It set the headers with:\n # Academy: 1\n # ThingOfImportance: potato\n self.bc.request.set_headers(academy=1, thing_of_importance='potato')\n ```\n \"\"\"\nheaders = {}\nitems = [\nindex for index in kargs\nif kargs[index] and (isinstance(kargs[index], str) or isinstance(kargs[index], int))\n]\nfor index in items:\nheaders[f'HTTP_{index.upper()}'] = str(kargs[index])\nself._parent.client.credentials(**headers)\n
"},{"location":"testing/mixins/bc/","title":"bc","text":""},{"location":"testing/mixins/bc/#breathecode.tests.mixins.breathecode_mixin.breathecode.Breathecode","title":"Breathecode
","text":" Bases: BreathecodeInterface
Collection of mixins for testing purposes
Source code inbreathecode/tests/mixins/breathecode_mixin/breathecode.py
class Breathecode(BreathecodeInterface):\n\"\"\"Collection of mixins for testing purposes\"\"\"\ncache: Cache\nrandom: Random\ndatetime: Datetime\nrequest: Request\ndatabase: Database\ncheck: Check\nformat: Format\nfake: Faker\ngarbage_collector: GarbageCollector\n_parent: APITestCase\ndef __init__(self, parent) -> None:\nself._parent = parent\nself.cache = Cache(parent, self)\nself.random = Random(parent, self)\nself.datetime = Datetime(parent, self)\nself.request = Request(parent, self)\nself.database = Database(parent, self)\nself.check = Check(parent, self)\nself.format = Format(parent, self)\nself.garbage_collector = GarbageCollector(parent, self)\nself.fake = fake\ndef help(self, *args) -> None:\n\"\"\"\n Print a list of mixin with a tree style (command of Linux).\n Usage:\n ```py\n # this print a tree with all the mixins\n self.bc.help()\n # this print just the docs of corresponding method\n self.bc.help('bc.datetime.now')\n ```\n \"\"\"\nif args:\nfor arg in args:\nself._get_doctring(arg)\nelse:\nself._help_tree()\n# prevent left a `self.bc.help()` in the code\nassert False\ndef _get_doctring(self, path: str) -> None:\nparts_of_path = path.split('.')\ncurrent_path = ''\ncurrent = None\nfor part_of_path in parts_of_path:\nif not current:\nif not hasattr(self._parent, part_of_path):\ncurrent_path += f'.{part_of_path}'\nbreak\ncurrent = getattr(self._parent, part_of_path)\nelse:\nif not hasattr(current, part_of_path):\ncurrent_path += f'.{part_of_path}'\ncurrent = None\nbreak\ncurrent = getattr(current, part_of_path)\nif current:\nfrom unittest.mock import patch, MagicMock\nif callable(current):\nprint(f'self.{path}{print_arguments(current)}:')\nelse:\nprint(f'self.{path}:')\nprint()\nwith patch('sys.stdout.write', MagicMock()) as mock:\nhelp(current)\nfor args, _ in mock.call_args_list:\nif args[0] == '\\n':\nprint()\nlines = args[0].split('\\n')\nfor line in lines[3:-1]:\nprint(f' {line}')\nelse:\nprint(f'self.{path}:')\nprint()\nprint(f' self{current_path} not exists.')\nprint()\ndef _help_tree(self, level: int = 0, parent: Optional[dict] = None, last_item: bool = False) -> list[str]:\n\"\"\"Print a list of mixin with a tree style (command of Linux)\"\"\"\nresult: list[str] = []\nif not parent:\nresult.append('bc')\nparent = [x for x in dir(parent or self) if not x.startswith('_')]\nif last_item:\nstarts = ' ' + ('\u2502 ' * (level - 1))\nelse:\nstarts = '\u2502 ' * level\nfor key in parent:\nitem = getattr(self, key)\nif callable(item):\nresult.append(f'{starts}\u251c\u2500\u2500 {key}{print_arguments(item)}')\nelse:\nresult.append(f'{starts}\u251c\u2500\u2500 {key}')\nlast_item = parent.index(key) == len(parent) - 1\nresult = [*result, *Breathecode._help_tree(item, level + 1, item, last_item)]\nresult[-1] = result[-1].replace(' \u251c\u2500\u2500 ', ' \u2514\u2500\u2500 ')\nresult[-1] = result[-1].replace(r'\u251c\u2500\u2500 ([a-zA-Z0-9]+)$', r'\u2514\u2500\u2500 \\1')\nfor n in range(len(result) - 1, -1, -1):\nif result[n][0] == '\u251c':\nresult[n] = re.sub(r'^\u251c', r'\u2514', result[n])\nbreak\nif level == 0:\nprint('\\n'.join(result))\nreturn result\n
"},{"location":"testing/mixins/bc/#breathecode.tests.mixins.breathecode_mixin.breathecode.Breathecode.help","title":"help(*args)
","text":"Print a list of mixin with a tree style (command of Linux).
Usage:
# this print a tree with all the mixins\nself.bc.help()\n# this print just the docs of corresponding method\nself.bc.help('bc.datetime.now')\n
Source code in breathecode/tests/mixins/breathecode_mixin/breathecode.py
def help(self, *args) -> None:\n\"\"\"\n Print a list of mixin with a tree style (command of Linux).\n Usage:\n ```py\n # this print a tree with all the mixins\n self.bc.help()\n # this print just the docs of corresponding method\n self.bc.help('bc.datetime.now')\n ```\n \"\"\"\nif args:\nfor arg in args:\nself._get_doctring(arg)\nelse:\nself._help_tree()\n# prevent left a `self.bc.help()` in the code\nassert False\n
"},{"location":"testing/mocks/mock-requests/","title":"Mock requests","text":"Mocks for requests
module
apply_requests_delete_mock(endpoints=[])
","text":"Apply a mock to requests.delete
.
Usage:
import requests\nfrom unittest.mock import patch, call\nfrom breathecode.tests.mocks import apply_requests_delete_mock\nfrom breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n@patch('requests.delete', apply_requests_delete_mock([\n204,\n'https://www.eventbriteapi.com/v3/events/1/structured_content/',\nNone,\n]))\ndef test_xyz():\ndelete_eventbrite_descriptions_for_event(1)\nassert requests.delete.call_args_list == [\ncall('https://www.eventbriteapi.com/v3/events/1/structured_content/',\nheaders={'Authorization': f'Bearer 1234567890'},\ndata=None),\n]\n
Source code in breathecode/tests/mocks/requests/__init__.py
def apply_requests_delete_mock(endpoints=[]):\n\"\"\"\n Apply a mock to `requests.delete`.\n Usage:\n ```py\n import requests\n from unittest.mock import patch, call\n from breathecode.tests.mocks import apply_requests_delete_mock\n from breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n @patch('requests.delete', apply_requests_delete_mock([\n 204,\n 'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n None,\n ]))\n def test_xyz():\n delete_eventbrite_descriptions_for_event(1)\n assert requests.delete.call_args_list == [\n call('https://www.eventbriteapi.com/v3/events/1/structured_content/',\n headers={'Authorization': f'Bearer 1234567890'},\n data=None),\n ]\n ```\n \"\"\"\nreturn apply_requests_mock('DELETE', endpoints)\n
"},{"location":"testing/mocks/mock-requests/#breathecode.tests.mocks.requests.apply_requests_get_mock","title":"apply_requests_get_mock(endpoints=[])
","text":"Apply a mock to requests.get
.
Usage:
import requests\nfrom unittest.mock import patch, call\nfrom breathecode.tests.mocks import apply_requests_get_mock\nfrom breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n@patch('requests.get', apply_requests_get_mock([\n200,\n'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n{ 'data': { ... } },\n]))\ndef test_xyz():\nget_eventbrite_descriptions_for_event(1)\nassert requests.get.call_args_list == [\ncall('https://www.eventbriteapi.com/v3/events/1/structured_content/',\nheaders={'Authorization': f'Bearer 1234567890'},\ndata=None),\n]\n
Source code in breathecode/tests/mocks/requests/__init__.py
def apply_requests_get_mock(endpoints=[]):\n\"\"\"\n Apply a mock to `requests.get`.\n Usage:\n ```py\n import requests\n from unittest.mock import patch, call\n from breathecode.tests.mocks import apply_requests_get_mock\n from breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n @patch('requests.get', apply_requests_get_mock([\n 200,\n 'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n { 'data': { ... } },\n ]))\n def test_xyz():\n get_eventbrite_descriptions_for_event(1)\n assert requests.get.call_args_list == [\n call('https://www.eventbriteapi.com/v3/events/1/structured_content/',\n headers={'Authorization': f'Bearer 1234567890'},\n data=None),\n ]\n ```\n \"\"\"\nreturn apply_requests_mock('GET', endpoints)\n
"},{"location":"testing/mocks/mock-requests/#breathecode.tests.mocks.requests.apply_requests_head_mock","title":"apply_requests_head_mock(endpoints=[])
","text":"Apply a mock to requests.head
.
Usage:
import requests\nfrom unittest.mock import patch, call\nfrom breathecode.tests.mocks import apply_requests_head_mock\nfrom breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n@patch('requests.head', apply_requests_head_mock([\n200,\n'https://www.eventbriteapi.com/v3/events/1/structured_content/',\nNone,\n]))\ndef test_xyz():\nget_meta_for_eventbrite_description_for_event(1)\nassert requests.head.call_args_list == [\ncall('https://www.eventbriteapi.com/v3/events/1/structured_content/',\nheaders={'Authorization': f'Bearer 1234567890'},\ndata=None),\n]\n
Source code in breathecode/tests/mocks/requests/__init__.py
def apply_requests_head_mock(endpoints=[]):\n\"\"\"\n Apply a mock to `requests.head`.\n Usage:\n ```py\n import requests\n from unittest.mock import patch, call\n from breathecode.tests.mocks import apply_requests_head_mock\n from breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n @patch('requests.head', apply_requests_head_mock([\n 200,\n 'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n None,\n ]))\n def test_xyz():\n get_meta_for_eventbrite_description_for_event(1)\n assert requests.head.call_args_list == [\n call('https://www.eventbriteapi.com/v3/events/1/structured_content/',\n headers={'Authorization': f'Bearer 1234567890'},\n data=None),\n ]\n ```\n \"\"\"\nreturn apply_requests_mock('HEAD', endpoints)\n
"},{"location":"testing/mocks/mock-requests/#breathecode.tests.mocks.requests.apply_requests_mock","title":"apply_requests_mock(method='get', endpoints=[])
","text":"Apply Storage Blob Mock
Source code inbreathecode/tests/mocks/requests/__init__.py
def apply_requests_mock(method='get', endpoints=[]):\n\"\"\"Apply Storage Blob Mock\"\"\"\nmethod = method.lower()\nREQUESTS_INSTANCES[method] = request_mock(endpoints)\nreturn REQUESTS_INSTANCES[method]\n
"},{"location":"testing/mocks/mock-requests/#breathecode.tests.mocks.requests.apply_requests_patch_mock","title":"apply_requests_patch_mock(endpoints=[])
","text":"Apply a mock to requests.patch
.
Usage:
import requests\nfrom unittest.mock import patch, call\nfrom breathecode.tests.mocks import apply_requests_patch_mock\nfrom breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n@patch('requests.patch', apply_requests_patch_mock([\n200,\n'https://www.eventbriteapi.com/v3/events/1/structured_content/',\nNone,\n]))\ndef test_xyz():\npatch_eventbrite_descriptions_for_event(1)\nassert requests.patch.call_args_list == [\ncall('https://www.eventbriteapi.com/v3/events/1/structured_content/',\nheaders={'Authorization': f'Bearer 1234567890'},\ndata=None),\n]\n
Source code in breathecode/tests/mocks/requests/__init__.py
def apply_requests_patch_mock(endpoints=[]):\n\"\"\"\n Apply a mock to `requests.patch`.\n Usage:\n ```py\n import requests\n from unittest.mock import patch, call\n from breathecode.tests.mocks import apply_requests_patch_mock\n from breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n @patch('requests.patch', apply_requests_patch_mock([\n 200,\n 'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n None,\n ]))\n def test_xyz():\n patch_eventbrite_descriptions_for_event(1)\n assert requests.patch.call_args_list == [\n call('https://www.eventbriteapi.com/v3/events/1/structured_content/',\n headers={'Authorization': f'Bearer 1234567890'},\n data=None),\n ]\n ```\n \"\"\"\nreturn apply_requests_mock('PATCH', endpoints)\n
"},{"location":"testing/mocks/mock-requests/#breathecode.tests.mocks.requests.apply_requests_post_mock","title":"apply_requests_post_mock(endpoints=[])
","text":"Apply a mock to requests.post
.
Usage:
import requests\nfrom unittest.mock import patch, call\nfrom breathecode.tests.mocks import apply_requests_post_mock\nfrom breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n@patch('requests.post', apply_requests_post_mock([\n201,\n'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n{ 'data': { ... } },\n]))\ndef test_xyz():\npost_eventbrite_descriptions_for_event(1)\nassert requests.post.call_args_list == [\ncall('https://www.eventbriteapi.com/v3/events/1/structured_content/',\nheaders={'Authorization': f'Bearer 1234567890'},\ndata=None),\n]\n
Source code in breathecode/tests/mocks/requests/__init__.py
def apply_requests_post_mock(endpoints=[]):\n\"\"\"\n Apply a mock to `requests.post`.\n Usage:\n ```py\n import requests\n from unittest.mock import patch, call\n from breathecode.tests.mocks import apply_requests_post_mock\n from breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n @patch('requests.post', apply_requests_post_mock([\n 201,\n 'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n { 'data': { ... } },\n ]))\n def test_xyz():\n post_eventbrite_descriptions_for_event(1)\n assert requests.post.call_args_list == [\n call('https://www.eventbriteapi.com/v3/events/1/structured_content/',\n headers={'Authorization': f'Bearer 1234567890'},\n data=None),\n ]\n ```\n \"\"\"\nreturn apply_requests_mock('POST', endpoints)\n
"},{"location":"testing/mocks/mock-requests/#breathecode.tests.mocks.requests.apply_requests_put_mock","title":"apply_requests_put_mock(endpoints=[])
","text":"Apply a mock to requests.put
.
Usage:
import requests\nfrom unittest.mock import patch, call\nfrom breathecode.tests.mocks import apply_requests_put_mock\nfrom breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n@patch('requests.put', apply_requests_put_mock([\n200,\n'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n{ 'data': { ... } },\n]))\ndef test_xyz():\nput_eventbrite_descriptions_for_event(1)\nassert requests.put.call_args_list == [\ncall('https://www.eventbriteapi.com/v3/events/1/structured_content/',\nheaders={'Authorization': f'Bearer 1234567890'},\ndata=None),\n]\n
Source code in breathecode/tests/mocks/requests/__init__.py
def apply_requests_put_mock(endpoints=[]):\n\"\"\"\n Apply a mock to `requests.put`.\n Usage:\n ```py\n import requests\n from unittest.mock import patch, call\n from breathecode.tests.mocks import apply_requests_put_mock\n from breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n @patch('requests.put', apply_requests_put_mock([\n 200,\n 'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n { 'data': { ... } },\n ]))\n def test_xyz():\n put_eventbrite_descriptions_for_event(1)\n assert requests.put.call_args_list == [\n call('https://www.eventbriteapi.com/v3/events/1/structured_content/',\n headers={'Authorization': f'Bearer 1234567890'},\n data=None),\n ]\n ```\n \"\"\"\nreturn apply_requests_mock('PUT', endpoints)\n
"},{"location":"testing/mocks/mock-requests/#breathecode.tests.mocks.requests.apply_requests_request_mock","title":"apply_requests_request_mock(endpoints=[])
","text":"Apply a mock to requests.request
.
Usage:
import requests\nfrom unittest.mock import patch, call\nfrom breathecode.tests.mocks import apply_requests_request_mock\nfrom breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n@patch('requests.request', apply_requests_request_mock([\n200,\n'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n{ 'data': { ... } },\n]))\ndef test_xyz():\nget_eventbrite_description_for_event(1)\nassert requests.request.call_args_list == [\ncall('GET',\n'https://www.eventbriteapi.com/v3/events/1/structured_content/',\nheaders={'Authorization': f'Bearer 1234567890'},\ndata=None),\n]\n
Source code in breathecode/tests/mocks/requests/__init__.py
def apply_requests_request_mock(endpoints=[]):\n\"\"\"\n Apply a mock to `requests.request`.\n Usage:\n ```py\n import requests\n from unittest.mock import patch, call\n from breathecode.tests.mocks import apply_requests_request_mock\n from breathecode.is_doesnt_exists import get_eventbrite_description_for_event\n @patch('requests.request', apply_requests_request_mock([\n 200,\n 'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n { 'data': { ... } },\n ]))\n def test_xyz():\n get_eventbrite_description_for_event(1)\n assert requests.request.call_args_list == [\n call('GET',\n 'https://www.eventbriteapi.com/v3/events/1/structured_content/',\n headers={'Authorization': f'Bearer 1234567890'},\n data=None),\n ]\n ```\n \"\"\"\nreturn apply_requests_mock('REQUEST', endpoints)\n
"},{"location":"testing/mocks/using-mocks/","title":"Using mocks","text":""},{"location":"testing/mocks/using-mocks/#mock-object","title":"Mock object","text":"Mock objects are simulated objects that mimic the behavior of real objects in controlled ways, most often as part of a software testing initiative. A programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior of a human in vehicle impacts.
"},{"location":"testing/mocks/using-mocks/#how-to-apply-a-automatic-mock","title":"How to apply a automatic mock","text":""},{"location":"testing/mocks/using-mocks/#the-most-easier-way-to-create-a-mock","title":"The most easier way to create a mock
","text":"The decorator @patch.object
is the best option to implement a mock
@patch.object(object_class_or_module, 'method_or_function_to_be_mocked', MagicMock())\n
"},{"location":"testing/mocks/using-mocks/#this-is-the-code-to-test","title":"This is the code to test
","text":"# utils.py\nfrom .actions import shoot_gun, kenny_s_birth, show\ndef kenny_killer(kenny_id: int) -> None:\n# get the current kenny\nkenny = Kenny.objects.filter(id=kenny_id).first()\n# see - South Park - Coon and friends\nif kenny:\nshoot_gun(kenny)\nkenny_number = kenny_s_birth()\nshow(kenny_number)\n
"},{"location":"testing/mocks/using-mocks/#this-is-a-example-of-use-of-mocks","title":"This is a example of use of mocks
","text":"from unittest.mock import MagicMock, call, patch\nfrom rest_framework.test import APITestCase\nfrom .models import Kenny\nfrom .utils import kenny_killer\nimport app.actions as actions\n# this is a wrapper that implement the kenny_s_birth static behavior to the test\ndef kenny_s_birth_mock(number: int):\ndef kenny_s_birth():\nreturn number\n# the side_effect is a function that manage the behavior of the mocked function\nreturn MagicMock(side_effect=kenny_s_birth)\nclass KennyTestSuite(APITestCase):\n# \ud83d\udd3d this function is automatically mocked\n@patch.object(actions, 'shoot_gun', MagicMock())\n# \ud83d\udd3d this function is manually mocked\n@patch.object(actions, 'kenny_s_birth', kenny_s_birth_mock(1000))\n# \ud83d\udd3d this function is automatically mocked\n@patch.object(actions, 'show', MagicMock())\ndef test_kill_kenny(self):\nkenny = Kenny()\nkenny.save()\nkenny_killer(kenny_id=1)\n# shoot_gun() is called with a kenny instance\nself.assertEqual(actions.shoot_gun.call_args_list, [call(kenny)])\n# kenny_s_birth() is called with zero arguments\nself.assertEqual(actions.kenny_s_birth.call_args_list, [call()])\n# show is called\nself.assertEqual(actions.show.call_args_list, [call(1)])\n
"}]}
\ No newline at end of file
diff --git a/security/capabilities/index.html b/security/capabilities/index.html
new file mode 100644
index 000000000..6bbe547c6
--- /dev/null
+++ b/security/capabilities/index.html
@@ -0,0 +1,1414 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Authenticated users must belong to at least one academy with a specific role, each role has a series of capabilities that specify what any user with that role will be "capable" of doing.
+Authenticated methods must be decorated with the @capable_of
decorator in increase security validation. For example:
from breathecode.utils import capable_of
+ @capable_of('crud_member')
+ def post(self, request, academy_id=None):
+ serializer = StaffPOSTSerializer(data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
Any view decorated with the @capable_of must be used passing an academy id either:
+path('academy/<int:academy_id>/member', MemberView.as_view()),
Academy
header.This list is alive, it will grow and vary over time:
+CAPABILITIES = [
+ {
+ 'slug': 'read_my_academy',
+ 'description': 'Read your academy information'
+ },
+ {
+ 'slug': 'crud_my_academy',
+ 'description': 'Read, or update your academy information (very high level, almost the academy admin)'
+ },
+ {
+ 'slug': 'crud_member',
+ 'description': 'Create, update or delete academy members (very high level, almost the academy admin)'
+ },
+ {
+ 'slug': 'read_member',
+ 'description': 'Read academy staff member information'
+ },
+ {
+ 'slug': 'crud_student',
+ 'description': 'Create, update or delete students'
+ },
+ {
+ 'slug': 'read_student',
+ 'description': 'Read student information'
+ },
+ {
+ 'slug': 'read_invite',
+ 'description': 'Read invites from users'
+ },
+ {
+ 'slug': 'crud_invite',
+ 'description': 'Create, update or delete invites from users'
+ },
+ {
+ 'slug': 'invite_resend',
+ 'description': 'Resent invites for user academies'
+ },
+ {
+ 'slug': 'read_assignment',
+ 'description': 'Read assignment information'
+ },
+ {
+ 'slug':
+ 'read_assignment_sensitive_details',
+ 'description':
+ 'The mentor in residence is allowed to see aditional info about the task, like the "delivery url"'
+ },
+ {
+ 'slug': 'read_shortlink',
+ 'description': 'Access the list of marketing shortlinks'
+ },
+ {
+ 'slug': 'crud_shortlink',
+ 'description': 'Create, update and delete marketing short links'
+ },
+ {
+ 'slug': 'crud_assignment',
+ 'description': 'Update assignments'
+ },
+ {
+ 'slug': 'task_delivery_details',
+ 'description': 'Get delivery URL for a task, that url can be sent to students for delivery'
+ },
+ {
+ 'slug': 'read_certificate',
+ 'description': 'List and read all academy certificates'
+ },
+ {
+ 'slug': 'crud_certificate',
+ 'description': 'Create, update or delete student certificates'
+ },
+ {
+ 'slug': 'read_layout',
+ 'description': 'Read layouts to generate new certificates'
+ },
+ {
+ 'slug': 'read_syllabus',
+ 'description': 'List and read syllabus information'
+ },
+ {
+ 'slug': 'crud_syllabus',
+ 'description': 'Create, update or delete syllabus versions'
+ },
+ {
+ 'slug': 'read_organization',
+ 'description': 'Read academy organization details'
+ },
+ {
+ 'slug': 'crud_organization',
+ 'description': 'Update, create or delete academy organization details'
+ },
+ {
+ 'slug': 'read_event',
+ 'description': 'List and retrieve event information'
+ },
+ {
+ 'slug': 'crud_event',
+ 'description': 'Create, update or delete event information'
+ },
+ {
+ 'slug': 'read_all_cohort',
+ 'description': 'List all the cohorts or single cohort information'
+ },
+ {
+ 'slug': 'read_single_cohort',
+ 'description': 'single cohort information related to a user'
+ },
+ {
+ 'slug': 'crud_cohort',
+ 'description': 'Create, update or delete cohort info'
+ },
+ {
+ 'slug': 'read_eventcheckin',
+ 'description': 'List and read all the event_checkins'
+ },
+ {
+ 'slug': 'read_survey',
+ 'description': 'List all the nps answers'
+ },
+ {
+ 'slug': 'crud_survey',
+ 'description': 'Create, update or delete surveys'
+ },
+ {
+ 'slug': 'read_nps_answers',
+ 'description': 'List all the nps answers'
+ },
+ {
+ 'slug': 'read_lead',
+ 'description': 'List all the leads'
+ },
+ {
+ 'slug': 'read_won_lead',
+ 'description': 'List all the won leads'
+ },
+ {
+ 'slug': 'crud_lead',
+ 'description': 'Create, update or delete academy leads'
+ },
+ {
+ 'slug': 'read_review',
+ 'description': 'Read review for a particular academy'
+ },
+ {
+ 'slug': 'crud_review',
+ 'description': 'Create, update or delete academy reviews'
+ },
+ {
+ 'slug': 'read_media',
+ 'description': 'List all the medias'
+ },
+ {
+ 'slug': 'crud_media',
+ 'description': 'Create, update or delete academy medias'
+ },
+ {
+ 'slug': 'read_media_resolution',
+ 'description': 'List all the medias resolutions'
+ },
+ {
+ 'slug': 'crud_media_resolution',
+ 'description': 'Create, update or delete academy media resolutions'
+ },
+ {
+ 'slug': 'read_cohort_activity',
+ 'description': 'Read low level activity in a cohort (attendancy, etc.)'
+ },
+ {
+ 'slug': 'generate_academy_token',
+ 'description': 'Create a new token only to be used by the academy'
+ },
+ {
+ 'slug': 'get_academy_token',
+ 'description': 'Read the academy token'
+ },
+ {
+ 'slug': 'send_reset_password',
+ 'description': 'Generate a temporal token and resend forgot password link'
+ },
+ {
+ 'slug': 'read_activity',
+ 'description': 'List all the user activities'
+ },
+ {
+ 'slug': 'crud_activity',
+ 'description': 'Create, update or delete a user activities'
+ },
+ {
+ 'slug': 'read_assignment',
+ 'description': 'List all the assignments'
+ },
+ {
+ 'slug': 'crud_assignment',
+ 'description': 'Create, update or delete a assignment'
+ },
+ {
+ 'slug':
+ 'classroom_activity',
+ 'description':
+ 'To report student activities during the classroom or cohorts (Specially meant for teachers)'
+ },
+ {
+ 'slug': 'academy_reporting',
+ 'description': 'Get detailed reports about the academy activity'
+ },
+ {
+ 'slug': 'generate_temporal_token',
+ 'description': 'Generate a temporal token to reset github credential or forgot password'
+ },
+ {
+ 'slug': 'read_mentorship_service',
+ 'description': 'Get all mentorship services from one academy'
+ },
+ {
+ 'slug': 'crud_mentorship_service',
+ 'description': 'Create, delete or update all mentorship services from one academy'
+ },
+ {
+ 'slug': 'read_mentorship_mentor',
+ 'description': 'Get all mentorship mentors from one academy'
+ },
+ {
+ 'slug': 'crud_mentorship_mentor',
+ 'description': 'Create, delete or update all mentorship mentors from one academy'
+ },
+ {
+ 'slug': 'read_mentorship_session',
+ 'description': 'Get all session from one academy'
+ },
+ {
+ 'slug': 'crud_mentorship_session',
+ 'description': 'Create, delete or update all session from one academy'
+ },
+ {
+ 'slug': 'crud_freelancer_bill',
+ 'description': 'Create, delete or update all freelancer bills from one academy'
+ },
+ {
+ 'slug': 'read_freelancer_bill',
+ 'description': 'Read all all freelancer bills from one academy'
+ },
+ {
+ 'slug': 'crud_mentorship_bill',
+ 'description': 'Create, delete or update all mentroship bills from one academy'
+ },
+ {
+ 'slug': 'read_mentorship_bill',
+ 'description': 'Read all mentroship bills from one academy'
+ },
+ {
+ 'slug': 'read_asset',
+ 'description': 'Read all academy registry assets'
+ },
+ {
+ 'slug': 'crud_asset',
+ 'description': 'Update, create and delete registry assets'
+ },
+ {
+ 'slug': 'read_tag',
+ 'description': 'Read marketing tags and their details'
+ },
+ {
+ 'slug': 'crud_tag',
+ 'description': 'Update, create and delete a marketing tag and its details'
+ },
+ {
+ 'slug': 'get_gitpod_user',
+ 'description': 'List gitpod user the academy is consuming'
+ },
+ {
+ 'slug': 'update_gitpod_user',
+ 'description': 'Update gitpod user expiration based on available information'
+ },
+ {
+ 'slug': 'read_technology',
+ 'description': 'Read asset technologies'
+ },
+ {
+ 'slug': 'crud_technology',
+ 'description': 'Update, create and delete asset technologies'
+ },
+ {
+ 'slug': 'read_keyword',
+ 'description': 'Read SEO keywords'
+ },
+ {
+ 'slug': 'crud_keyword',
+ 'description': 'Update, create and delete SEO keywords'
+ },
+ {
+ 'slug': 'read_keywordcluster',
+ 'description': 'Update, create and delete asset technologies'
+ },
+ {
+ 'slug': 'crud_keywordcluster',
+ 'description': 'Update, create and delete asset technologies'
+ },
+]
+
https://cloud.google.com/functions/docs/writing/http
+https://console.cloud.google.com/functions/list
+https://cloud.google.com/functions/docs/testing/test-http#functions-testing-http-integration-python
+Name | +Activator | +Resource | +Repository | +
---|---|---|---|
process-zap | +HTTP | +process-zap | ++ |
screenshots | +HTTP | +screenshots | +jefer94/screenshots | +
resize-image | +HTTP | +resize-image | +breatheco-de/gcloud-resize-image | +
thumbnail-generator | +Bucket | +media-breathecode | +breatheco-de/gcloud-thumbnail-generator | +
thumbnail-generator-dev | +Bucket | +media-breathecode-dev | +breatheco-de/gcloud-thumbnail-generator | +
Storage
+
+
+Google Cloud Storage
+ + +breathecode/services/google_cloud/storage.py
file(bucket_name, file_name)
+
+Get File object
+ +Parameters:
+Name | +Type | +Description | +Default | +
---|---|---|---|
bucket_name |
+
+ str
+ |
+ Name of bucket in Google Cloud Storage |
+ + required + | +
file_name |
+
+ str
+ |
+ Name of blob in Google Cloud Bucket |
+ + required + | +
Returns:
+Name | Type | +Description | +
---|---|---|
File |
+ File
+ |
+ File object |
+
breathecode/services/google_cloud/storage.py
The following icons are being used for the slack integrations https://www.pngrepo.com/collection/soft-colored-ui-icons/1
+ + + + + + +The official documentation for django signals can be found here.
+At BreatheCode, signals are similar concept to "events", we use signals as custom "events" that can notify important things that happen in one app to all the other app's (if they are listening).
+For example: When a student drops from a cohort
+There is a signal to notify when a student educational status gets updated
, this is useful because other application may react to it. Here is the signal being initialized, here is being triggered/dispatched when a student gets saved and this is an example where the signal is being received on the breathecode.marketing.app to trigger some additional tasks within the system.
Inside the breathecode team, we see signals for asynchronous processing of any side effects, we try to focus on them for communication between apps only.
+You have many examples that you can find inside the code, each breathecode app has a file signals.py
that contains all the signals dispatched by that app. If the file does not exist within one of the apps, and you need to create a signal for that app, you can create the file yourself.
If you wanted to create a signal for when a cohort is saved, you should start by initializing it inside breathecode/admissions/signals.py
like this:
All the initialized signals are available on the same application signals.py
file, if the signal you want to dispatch is not there, you should probably declare a new one.
After the signal is initialized, it can be dispatched anywhere withing the same app, for example inside a serializer create method like this:
+from .signals import cohort_saved
+
+class CohortSerializer(CohortSerializerMixin):
+
+ def create(self, validated_data):
+ cohort = Cohort.objects.create(**validated_data, **self.context)
+ cohort_saved.send(instance=self, sender=CohortUser)
+ return cohort
+
All django applications can subscribe to receive a signal, even if those signals are coming from another app, but you should always add your receiving code inside the receivers.py of the app that will react to the signal.
+The following code will receive the cohort_saved
signal and print on the screen if its being created or updated.
Note: Its a good idea to always connect receivers to tasks, that way you can asynconosly pospone any processing that you will do after the cohort its created.
+from breathecode.admissions.signals import student_edu_status_updated, cohort_saved
+from .models import FormEntry, ActiveCampaignAcademy
+from .tasks import add_cohort_task_to_student, add_cohort_slug_as_acp_tag
+
+@receiver(cohort_saved, sender=Cohort)
+def cohort_post_save(sender, instance, created, *args, **kwargs):
+ if created:
+ print(f"The cohort {instance.id} was just created")
+ # you can call a task from task.py here.
+ else:
+ print(f"The cohort {instance.id} was just updated")
+
Check
+
+
+Mixin with the purpose of cover all the related with the custom asserts
+ + +breathecode/tests/mixins/breathecode_mixin/check.py
16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 |
|
calls(first, second)
+
+Fail if the two objects are partially unequal as determined by the '==' operator.
+Usage:
+ + +breathecode/tests/mixins/breathecode_mixin/check.py
datetime_in_range(start, end, date)
+
+Check if a range if between start and end argument.
+Usage:
+from django.utils import timezone
+
+start = timezone.now()
+in_range = timezone.now()
+end = timezone.now()
+out_of_range = timezone.now()
+
+# pass because this datetime is between start and end
+self.bc.check.datetime_in_range(start, end, in_range) # 🟢
+
+# fail because this datetime is not between start and end
+self.bc.check.datetime_in_range(start, end, out_of_range) # 🔴
+
breathecode/tests/mixins/breathecode_mixin/check.py
list_with_pks(query, pks)
+
+Check if the list have the following primary keys.
+Usage:
+from breathecode.admissions.models import Cohort, Academy
+
+model = self.bc.database.create(cohort=1)
+
+collection = [model.cohort]
+
+# pass because the QuerySet has the primary keys 1
+self.bc.check.list_with_pks(collection, [1]) # 🟢
+
+# fail because the QuerySet has the primary keys 1 but the second argument is empty
+self.bc.check.list_with_pks(collection, []) # 🔴
+
breathecode/tests/mixins/breathecode_mixin/check.py
partial_equality(first, second)
+
+Fail if the two objects are partially unequal as determined by the '==' operator.
+Usage:
+obj1 = {'key1': 1, 'key2': 2}
+obj2 = {'key2': 2, 'key3': 1}
+obj3 = {'key2': 2}
+
+# it's fail because the key3 is not in the obj1
+self.bc.check.partial_equality(obj1, obj2) # 🔴
+
+# it's fail because the key1 is not in the obj2
+self.bc.check.partial_equality(obj2, obj1) # 🔴
+
+# it's pass because the key2 exists in the obj1
+self.bc.check.partial_equality(obj1, obj3) # 🟢
+
+# it's pass because the key2 exists in the obj2
+self.bc.check.partial_equality(obj2, obj3) # 🟢
+
+# it's fail because the key1 is not in the obj3
+self.bc.check.partial_equality(obj3, obj1) # 🔴
+
+# it's fail because the key3 is not in the obj3
+self.bc.check.partial_equality(obj3, obj2) # 🔴
+
breathecode/tests/mixins/breathecode_mixin/check.py
queryset_of(query, model)
+
+Check if the first argument is a queryset of a models provided as second argument.
+Usage:
+from breathecode.admissions.models import Cohort, Academy
+
+self.bc.database.create(cohort=1)
+
+collection = []
+queryset = Cohort.objects.filter()
+
+# pass because the first argument is a QuerySet and it's type Cohort
+self.bc.check.queryset_of(queryset, Cohort) # 🟢
+
+# fail because the first argument is a QuerySet and it is not type Academy
+self.bc.check.queryset_of(queryset, Academy) # 🔴
+
+# fail because the first argument is not a QuerySet
+self.bc.check.queryset_of(collection, Academy) # 🔴
+
breathecode/tests/mixins/breathecode_mixin/check.py
queryset_with_pks(query, pks)
+
+Check if the queryset have the following primary keys.
+Usage:
+from breathecode.admissions.models import Cohort, Academy
+
+self.bc.database.create(cohort=1)
+
+collection = []
+queryset = Cohort.objects.filter()
+
+# pass because the QuerySet has the primary keys 1
+self.bc.check.queryset_with_pks(queryset, [1]) # 🟢
+
+# fail because the QuerySet has the primary keys 1 but the second argument is empty
+self.bc.check.queryset_with_pks(queryset, []) # 🔴
+
breathecode/tests/mixins/breathecode_mixin/check.py
Database
+
+
+Mixin with the purpose of cover all the related with the database
+ + +breathecode/tests/mixins/breathecode_mixin/database.py
26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 |
|
async_count(path)
+
+This is a wrapper for Model.objects.count()
, get how many instances of this Model
are saved.
Keywords arguments:
+- path(str
): path to a model, for example admissions.CohortUser
.
breathecode/tests/mixins/breathecode_mixin/database.py
async_create(*args, **kwargs)
+
+This is a wrapper for Model.objects.count()
, get how many instances of this Model
are saved.
Keywords arguments:
+- path(str
): path to a model, for example admissions.CohortUser
.
breathecode/tests/mixins/breathecode_mixin/database.py
async_get(path, pk, dict=True)
+
+This is a wrapper for Model.objects.filter(pk=pk).first()
, get the values of model as dict
if
+dict=True
else get the Model
instance.
Keywords arguments:
+- path(str
): path to a model, for example admissions.CohortUser
.
+- pk(str | int
): primary key of model.
+- dict(bool
): if true return dict of values of model else return model instance.
breathecode/tests/mixins/breathecode_mixin/database.py
async_list_of(path, dict=True)
+
+This is a wrapper for Model.objects.filter()
, get a list of values of models as list[dict]
if
+dict=True
else get a list of Model
instances.
Keywords arguments:
+- path(str
): path to a model, for example admissions.CohortUser
.
+- dict(bool
): if true return dict of values of model else return model instance.
breathecode/tests/mixins/breathecode_mixin/database.py
count(path)
+
+This is a wrapper for Model.objects.count()
, get how many instances of this Model
are saved.
Usage:
+ +Keywords arguments:
+- path(str
): path to a model, for example admissions.CohortUser
.
breathecode/tests/mixins/breathecode_mixin/database.py
create(*args, **kwargs)
+
+Create one o many instances of models and return it like a dict of models.
+Usage:
+# create three users
+self.bc.database.create(user=3)
+
+# create one user with a specific first name
+user = {'first_name': 'Lacey'}
+self.bc.database.create(user=user)
+
+# create two users with a specific first name and last name
+users = [
+ {'first_name': 'Lacey', 'last_name': 'Sturm'},
+ {'first_name': 'The', 'last_name': 'Warning'},
+]
+self.bc.database.create(user=users)
+
+# create two users with the same first name
+user = {'first_name': 'Lacey'}
+self.bc.database.create(user=(2, user))
+
+# setting up manually the relationships
+cohort_user = {'cohort_id': 2}
+self.bc.database.create(cohort=2, cohort_user=cohort_user)
+
It get the model name as snake case, you can pass a bool
, int
, dict
, tuple
, list[dict]
or
+list[tuple]
.
Behavior for type of argument:
+bool
: if it is true generate a instance of a model.int
: generate a instance of a model n times, if n
> 1 this is a list.dict
: generate a instance of a model, this pass to mixer.blend custom values to the model.tuple
: one element need to be a int and the other be a dict, generate a instance of a model n times,
+if n
> 1 this is a list, this pass to mixer.blend custom values to the model.list[dict]
: generate a instance of a model n times, if n
> 1 this is a list,
+this pass to mixer.blend custom values to the model.list[tuple]
: generate a instance of a model n times, if n
> 1 this is a list for each element,
+this pass to mixer.blend custom values to the model.Keywords arguments deprecated:
+- models: this arguments is use to implement inheritance, receive as argument the output of other
+self.bc.database.create()
execution.
+- authenticate: create a user and use APITestCase.client.force_authenticate(user=models['user'])
to
+get credentials.
breathecode/tests/mixins/breathecode_mixin/database.py
create_v2(*args, **kwargs)
+
+Unstable version of mixin that create all models, do not use this.
+ +breathecode/tests/mixins/breathecode_mixin/database.py
delete(path, pk=None)
+
+This is a wrapper for Model.objects.filter(pk=pk).delete()
, delete a element if pk
is provided else
+all the entries.
Usage:
+# create 19110911 cohorts 🦾
+self.bc.database.create(cohort=19110911)
+
+# exists 19110911 cohorts 🦾
+self.assertEqual(self.bc.database.count('admissions.Cohort'), 19110911)
+
+# remove all the cohorts
+self.bc.database.delete(10)
+
+# exists 19110910 cohorts
+self.assertEqual(self.bc.database.count('admissions.Cohort'), 19110910)
+
self.bc.database.delete()
+self.assertEqual(self.bc.database.count('admissions.Cohort'), 0) +```
+Keywords arguments:
+- path(str
): path to a model, for example admissions.CohortUser
.
+- pk(str | int
): primary key of model.
breathecode/tests/mixins/breathecode_mixin/database.py
get(path, pk, dict=True)
+
+This is a wrapper for Model.objects.filter(pk=pk).first()
, get the values of model as dict
if
+dict=True
else get the Model
instance.
Usage:
+# get the Cohort with the pk 1 as dict
+self.bc.database.get('admissions.Cohort', 1)
+
+# get the Cohort with the pk 1 as instance of model
+self.bc.database.get('admissions.Cohort', 1, dict=False)
+
Keywords arguments:
+- path(str
): path to a model, for example admissions.CohortUser
.
+- pk(str | int
): primary key of model.
+- dict(bool
): if true return dict of values of model else return model instance.
breathecode/tests/mixins/breathecode_mixin/database.py
get_model(path)
+
+
+ classmethod
+
+
+Return the model matching the given app_label and model_name.
+As a shortcut, app_label may be in the form
model_name is case-insensitive.
+Raise LookupError if no application exists with this label, or no +model exists with this name in the application. Raise ValueError if +called with a single argument that doesn't contain exactly one dot.
+Usage:
+# class breathecode.admissions.models.Cohort
+Cohort = self.bc.database.get_model('admissions.Cohort')
+
Keywords arguments:
+- path(str
): path to a model, for example admissions.CohortUser
.
breathecode/tests/mixins/breathecode_mixin/database.py
list_of(path, dict=True)
+
+This is a wrapper for Model.objects.filter()
, get a list of values of models as list[dict]
if
+dict=True
else get a list of Model
instances.
Usage:
+# get all the Cohort as list of dict
+self.bc.database.get('admissions.Cohort')
+
+# get all the Cohort as list of instances of model
+self.bc.database.get('admissions.Cohort', dict=False)
+
Keywords arguments:
+- path(str
): path to a model, for example admissions.CohortUser
.
+- dict(bool
): if true return dict of values of model else return model instance.
breathecode/tests/mixins/breathecode_mixin/database.py
Datetime
+
+
+Mixin with the purpose of cover all the related with datetime
+ + +breathecode/tests/mixins/breathecode_mixin/datetime.py
from_timedelta(delta=timedelta(seconds=0))
+
+Transform from timedelta to the totals seconds in str.
+Usage:
+from datetime import timedelta
+delta = timedelta(seconds=777)
+self.bc.datetime.from_timedelta(delta) # equals to '777.0'
+
breathecode/tests/mixins/breathecode_mixin/datetime.py
to_datetime_integer(timezone, date)
+
+Transform datetime to datetime integer.
+Usage:
+utc_now = timezone.now()
+
+# date
+date = datetime.datetime(2022, 3, 21, 2, 51, 55, 068)
+
+# equals to 202203210751
+self.bc.datetime.to_datetime_integer('america/new_york', date)
+
breathecode/tests/mixins/breathecode_mixin/datetime.py
Represents a instance of Faker you can learn about it in their webside
+ + + + + + +Format
+
+
+Mixin with the purpose of cover all the related with format or parse something
+ + +breathecode/tests/mixins/breathecode_mixin/format.py
100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 |
|
call(*args, **kwargs)
+
+Wraps a call into it and return its args and kwargs.
+example:
+args, kwargs = self.bc.format.call(2, 3, 4, a=1, b=2, c=3)
+
+assert args == (2, 3, 4)
+assert kwargs == {'a': 1, 'b': 2, 'c': 3}
+
breathecode/tests/mixins/breathecode_mixin/format.py
describe_models(models)
+
+Describe the models.
+Usage:
+# setup the database
+model = self.bc.database.create(user=1, cohort=1)
+
+# print the docstring to the corresponding test
+self.bc.format.describe_models(model)
+
breathecode/tests/mixins/breathecode_mixin/format.py
from_base64(hash)
+
+Transform a base64 hash to string.
+ +breathecode/tests/mixins/breathecode_mixin/format.py
from_bytes(s, encode=ENCODE)
+
+lookup(lang, overwrite=dict(), **kwargs)
+
+Generate from lookups the values in test side to be used in querystring.
+example:
+query = self.bc.format.lookup(
+ 'en',
+ strings={
+ 'exact': [
+ 'remote_meeting_url',
+ ],
+ },
+ bools={
+ 'is_null': ['ended_at'],
+ },
+ datetimes={
+ 'gte': ['starting_at'],
+ 'lte': ['ending_at'],
+ },
+ slugs=[
+ 'cohort_time_slot__cohort',
+ 'cohort_time_slot__cohort__academy',
+ 'cohort_time_slot__cohort__syllabus_version__syllabus',
+ ],
+ overwrite={
+ 'cohort': 'cohort_time_slot__cohort',
+ 'academy': 'cohort_time_slot__cohort__academy',
+ 'syllabus': 'cohort_time_slot__cohort__syllabus_version__syllabus',
+ 'start': 'starting_at',
+ 'end': 'ending_at',
+ 'upcoming': 'ended_at',
+ },
+)
+
+url = reverse_lazy('events:me_event_liveclass') + '?' + self.bc.format.querystring(query)
+
+# this test avoid to pass a invalid param to ORM
+response = self.client.get(url)
+
breathecode/tests/mixins/breathecode_mixin/format.py
142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 |
|
queryset(query)
+
+querystring(query)
+
+table(arg)
+
+Convert a QuerySet in a list.
+Usage:
+model = self.bc.database.create(user=1, group=1)
+
+self.bc.format.model(model.user.groups.all()) # = [{...}]
+
breathecode/tests/mixins/breathecode_mixin/format.py
to_base64(string)
+
+Transform a base64 hash to string.
+ +breathecode/tests/mixins/breathecode_mixin/format.py
to_decimal_string(decimal)
+
+Parse a number to the django representation of a decimal.
+Usage:
+ + +breathecode/tests/mixins/breathecode_mixin/format.py
to_dict(arg)
+
+Parse the object to a dict
or list[dict]
.
Usage:
+# setup the database, model.user is instance of dict and model.cohort
+# is instance list of dicts
+model = self.bc.database.create(user=1, cohort=2)
+
+# Parsing one model to a dict
+self.bc.format.to_dict(model.user) # = {...}
+
+# Parsing many models to a list of dict (infered from the type of
+# argument)
+self.bc.format.to_dict(model.cohort) # = [{...}, {...}]
+
breathecode/tests/mixins/breathecode_mixin/format.py
to_querystring(params)
+
+Random
+
+
+Mixin with the purpose of cover all the related with the custom asserts
+ + +breathecode/tests/mixins/breathecode_mixin/random.py
file()
+
+Generate a random file.
+Usage:
+ + +breathecode/tests/mixins/breathecode_mixin/random.py
image(width=10, height=10, ext='png')
+
+Generate a random image.
+Usage:
+# generate a random image with width of 20px and height of 10px
+file, filename = self.bc.random.image(20, 10)
+
breathecode/tests/mixins/breathecode_mixin/random.py
Request
+
+
+Mixin with the purpose of cover all the related with the request
+ + +breathecode/tests/mixins/breathecode_mixin/request.py
authenticate(user)
+
+Forces authentication in a request inside a APITestCase.
+Usage:
+# setup the database
+model = self.bc.database.create(user=1)
+
+# that setup the request to use the credential of user passed
+self.bc.request.authenticate(model.user)
+
Keywords arguments:
+breathecode.authenticate.models.User
breathecode/tests/mixins/breathecode_mixin/request.py
manual_authentication(user)
+
+Generate a manual authentication using a token, this method is more slower than authenticate
.
# setup the database
+model = self.bc.database.create(user=1)
+
+# that setup the request to use the credential with tokens of user passed
+self.bc.request.manual_authentication(model.user)
+
Keywords arguments:
+breathecode.authenticate.models.User
.breathecode/tests/mixins/breathecode_mixin/request.py
set_headers(**kargs)
+
+Set headers.
+# It set the headers with:
+# Academy: 1
+# ThingOfImportance: potato
+self.bc.request.set_headers(academy=1, thing_of_importance='potato')
+
breathecode/tests/mixins/breathecode_mixin/request.py
Breathecode
+
+
+
+ Bases: BreathecodeInterface
Collection of mixins for testing purposes
+ + +breathecode/tests/mixins/breathecode_mixin/breathecode.py
31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 |
|
help(*args)
+
+Print a list of mixin with a tree style (command of Linux).
+Usage:
+# this print a tree with all the mixins
+self.bc.help()
+
+# this print just the docs of corresponding method
+self.bc.help('bc.datetime.now')
+
breathecode/tests/mixins/breathecode_mixin/breathecode.py
Mocks for requests
module
apply_requests_delete_mock(endpoints=[])
+
+Apply a mock to requests.delete
.
Usage:
+import requests
+from unittest.mock import patch, call
+from breathecode.tests.mocks import apply_requests_delete_mock
+from breathecode.is_doesnt_exists import get_eventbrite_description_for_event
+
+@patch('requests.delete', apply_requests_delete_mock([
+ 204,
+ 'https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ None,
+]))
+def test_xyz():
+ delete_eventbrite_descriptions_for_event(1)
+
+ assert requests.delete.call_args_list == [
+ call('https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ headers={'Authorization': f'Bearer 1234567890'},
+ data=None),
+ ]
+
breathecode/tests/mocks/requests/__init__.py
apply_requests_get_mock(endpoints=[])
+
+Apply a mock to requests.get
.
Usage:
+import requests
+from unittest.mock import patch, call
+from breathecode.tests.mocks import apply_requests_get_mock
+from breathecode.is_doesnt_exists import get_eventbrite_description_for_event
+
+@patch('requests.get', apply_requests_get_mock([
+ 200,
+ 'https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ { 'data': { ... } },
+]))
+def test_xyz():
+ get_eventbrite_descriptions_for_event(1)
+
+ assert requests.get.call_args_list == [
+ call('https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ headers={'Authorization': f'Bearer 1234567890'},
+ data=None),
+ ]
+
breathecode/tests/mocks/requests/__init__.py
apply_requests_head_mock(endpoints=[])
+
+Apply a mock to requests.head
.
Usage:
+import requests
+from unittest.mock import patch, call
+from breathecode.tests.mocks import apply_requests_head_mock
+from breathecode.is_doesnt_exists import get_eventbrite_description_for_event
+
+@patch('requests.head', apply_requests_head_mock([
+ 200,
+ 'https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ None,
+]))
+def test_xyz():
+ get_meta_for_eventbrite_description_for_event(1)
+
+ assert requests.head.call_args_list == [
+ call('https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ headers={'Authorization': f'Bearer 1234567890'},
+ data=None),
+ ]
+
breathecode/tests/mocks/requests/__init__.py
apply_requests_mock(method='get', endpoints=[])
+
+Apply Storage Blob Mock
+ + +apply_requests_patch_mock(endpoints=[])
+
+Apply a mock to requests.patch
.
Usage:
+import requests
+from unittest.mock import patch, call
+from breathecode.tests.mocks import apply_requests_patch_mock
+from breathecode.is_doesnt_exists import get_eventbrite_description_for_event
+
+@patch('requests.patch', apply_requests_patch_mock([
+ 200,
+ 'https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ None,
+]))
+def test_xyz():
+ patch_eventbrite_descriptions_for_event(1)
+
+ assert requests.patch.call_args_list == [
+ call('https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ headers={'Authorization': f'Bearer 1234567890'},
+ data=None),
+ ]
+
breathecode/tests/mocks/requests/__init__.py
apply_requests_post_mock(endpoints=[])
+
+Apply a mock to requests.post
.
Usage:
+import requests
+from unittest.mock import patch, call
+from breathecode.tests.mocks import apply_requests_post_mock
+from breathecode.is_doesnt_exists import get_eventbrite_description_for_event
+
+@patch('requests.post', apply_requests_post_mock([
+ 201,
+ 'https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ { 'data': { ... } },
+]))
+def test_xyz():
+ post_eventbrite_descriptions_for_event(1)
+
+ assert requests.post.call_args_list == [
+ call('https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ headers={'Authorization': f'Bearer 1234567890'},
+ data=None),
+ ]
+
breathecode/tests/mocks/requests/__init__.py
apply_requests_put_mock(endpoints=[])
+
+Apply a mock to requests.put
.
Usage:
+import requests
+from unittest.mock import patch, call
+from breathecode.tests.mocks import apply_requests_put_mock
+from breathecode.is_doesnt_exists import get_eventbrite_description_for_event
+
+@patch('requests.put', apply_requests_put_mock([
+ 200,
+ 'https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ { 'data': { ... } },
+]))
+def test_xyz():
+ put_eventbrite_descriptions_for_event(1)
+
+ assert requests.put.call_args_list == [
+ call('https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ headers={'Authorization': f'Bearer 1234567890'},
+ data=None),
+ ]
+
breathecode/tests/mocks/requests/__init__.py
apply_requests_request_mock(endpoints=[])
+
+Apply a mock to requests.request
.
Usage:
+import requests
+from unittest.mock import patch, call
+from breathecode.tests.mocks import apply_requests_request_mock
+from breathecode.is_doesnt_exists import get_eventbrite_description_for_event
+
+@patch('requests.request', apply_requests_request_mock([
+ 200,
+ 'https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ { 'data': { ... } },
+]))
+def test_xyz():
+ get_eventbrite_description_for_event(1)
+
+ assert requests.request.call_args_list == [
+ call('GET',
+ 'https://www.eventbriteapi.com/v3/events/1/structured_content/',
+ headers={'Authorization': f'Bearer 1234567890'},
+ data=None),
+ ]
+
breathecode/tests/mocks/requests/__init__.py
Mock objects are simulated objects that mimic the behavior of real objects in controlled ways, most often as part of a software testing initiative. A programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior of a human in vehicle impacts.
+The most easier way to create a mock
The decorator @patch.object
is the best option to implement a mock
This is the code to test
# utils.py
+from .actions import shoot_gun, kenny_s_birth, show
+
+def kenny_killer(kenny_id: int) -> None:
+ # get the current kenny
+ kenny = Kenny.objects.filter(id=kenny_id).first()
+
+ # see - South Park - Coon and friends
+ if kenny:
+ shoot_gun(kenny)
+ kenny_number = kenny_s_birth()
+ show(kenny_number)
+
This is a example of use of mocks
from unittest.mock import MagicMock, call, patch
+from rest_framework.test import APITestCase
+from .models import Kenny
+from .utils import kenny_killer
+
+import app.actions as actions
+
+# this is a wrapper that implement the kenny_s_birth static behavior to the test
+def kenny_s_birth_mock(number: int):
+ def kenny_s_birth():
+ return number
+
+ # the side_effect is a function that manage the behavior of the mocked function
+ return MagicMock(side_effect=kenny_s_birth)
+
+class KennyTestSuite(APITestCase):
+ # 🔽 this function is automatically mocked
+ @patch.object(actions, 'shoot_gun', MagicMock())
+
+ # 🔽 this function is manually mocked
+ @patch.object(actions, 'kenny_s_birth', kenny_s_birth_mock(1000))
+
+ # 🔽 this function is automatically mocked
+ @patch.object(actions, 'show', MagicMock())
+
+ def test_kill_kenny(self):
+ kenny = Kenny()
+ kenny.save()
+
+ kenny_killer(kenny_id=1)
+
+ # shoot_gun() is called with a kenny instance
+ self.assertEqual(actions.shoot_gun.call_args_list, [call(kenny)])
+
+ # kenny_s_birth() is called with zero arguments
+ self.assertEqual(actions.kenny_s_birth.call_args_list, [call()])
+
+ # show is called
+ self.assertEqual(actions.show.call_args_list, [call(1)])
+
pipenv run doctor
or python -m scripts.doctor
.uname -a
.pipenv run docker_build_shell
.docker-compose run bc-shell
pipenv run test
, pipenv run ptest
, pipenv run cov
or pipenv run pcov
.