When I run pytest
locally, it works well. But it fails on GitHub Actions.
I set a secret key on GitHub secrets and uploaded .env.test
to the GitHub repository both.
Is it a dotenv
package problem or something else?
workflow.yml
name: Django CI
on: [pull_request, push] # activates the workflow when there is a push or pull request in the repo
env:
SERVER_ENV: "test"
DEBUG: ${{secrets.SECRET_KEY}}
SECRET_KEY: ${{secrets.SECRET_KEY}}
DB_ENGINE: ${{secrets.DB_ENGINE}}
DB_NAME: ${{secrets.DB_NAME}}
USER: ${{secrets.USER}}
PASSWORD: ${{secrets.PASSWORD}}
POSTGRES_USER: ${{secrets.POSTGRES_USER}}
POSTGRES_PASSWORD: ${{secrets.POSTGRES_PASSWORD}}
POSTGRES_DB: ${{secrets.POSTGRES_DB}}
HOST: ${{secrets.HOST}}
PORT: ${{secrets.PORT}}
CELERY_BROKER_URL: ${{secrets.CELERY_BROKER_URL}}
LOG_LEVEL: ${{secrets.LOG_LEVEL}}
NAVER_NEWS_API_CLIENT_ID: ${{secrets.NAVER_NEWS_API_CLIENT_ID}}
NAVER_NEWS_API_CLIENT_SECRET: ${{secrets.NAVER_NEWS_API_CLIENT_SECRET}}
DJANGO_ALLOWED_HOSTS: ${{secrets.DJANGO_ALLOWED_HOSTS}}
jobs:
test_project:
runs-on: ubuntu-latest # operating system your code will run on
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install Dependencies
run: |
pip install --upgrade pip
python3.8 -m venv env
source env/bin/activate
pip install -r requirements.txt # install all our dependencies for the project
- name: Run Pytest
run: pytest . # run pytest test
I tried like this
workflow.yml
jobs:
test_project:
runs-on: ubuntu-latest # operating system your code will run on
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Create venv
run: |
pip install --upgrade pip
python3.8 -m venv venv
source venv/bin/activate
- name: check python version
run: python -V
- name: check python version
run: venv/bin/python -V
- name: Install Dependencies
run: venv/bin/python -m pip install -r requirements.txt # install all our dependencies for the project
- name: Run Pytest
run: venv/bin/python -m pytest
Error log:
Traceback (most recent call last):
File "/opt/hostedtoolcache/Python/3.11.1/x64/bin/pytest", line 8, in <module>
sys.exit(console_main())
^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/_pytest/config/__init__.py", line 190, in console_main
code = main()
^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/_pytest/config/__init__.py", line 148, in main
config = _prepareconfig(args, plugins)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/_pytest/config/__init__.py", line 329, in _prepareconfig
config = pluginmanager.hook.pytest_cmdline_parse(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/pluggy/_hooks.py", line 265, in __call__
return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/pluggy/_manager.py", line 80, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/pluggy/_callers.py", line 55, in _multicall
gen.send(outcome)
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/_pytest/helpconfig.py", line 103, in pytest_cmdline_parse
config: Config = outcome.get_result()
^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/pluggy/_result.py", line 60, in get_result
raise ex[1].with_traceback(ex[2])
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/pluggy/_callers.py", line 39, in _multicall
res = hook_impl.function(*args)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/_pytest/config/__init__.py", line 1058, in pytest_cmdline_parse
self.parse(args)
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/_pytest/config/__init__.py", line 1346, in parse
self._preparse(args, addopts=addopts)
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/_pytest/config/__init__.py", line 1248, in _preparse
self.hook.pytest_load_initial_conftests(
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/pluggy/_hooks.py", line 265, in __call__
return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/pluggy/_manager.py", line 80, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/pluggy/_callers.py", line 60, in _multicall
return outcome.get_result()
^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/pluggy/_result.py", line 60, in get_result
raise ex[1].with_traceback(ex[2])
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/pluggy/_callers.py", line 39, in _multicall
res = hook_impl.function(*args)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.11.1/x64/lib/python3.11/site-packages/pytest_django_dotenv/plugin.py", line 17, in pytest_load_initial_conftests
dotenv.read_dotenv(os.path.join(virtual_env_path, f'../{early_config.getini("env_path")[0]}'))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen posixpath>", line 76, in join
TypeError: expected str, bytes or os.PathLike object, not NoneType
Error: Process completed with exit code 1.
requirements.txt
amqp==5.1.1
asgiref==3.6.0
async-timeout==4.0.2
attrs==22.2.0
backports.zoneinfo==0.2.1;python_version<"3.9"
beautifulsoup4==4.11.1
billiard==3.6.4.0
black==22.12.0
celery==5.2.7
certifi==2022.12.7
charset-normalizer==2.1.1
click==8.1.3
click-didyoumean==0.3.0
click-plugins==1.1.1
click-repl==0.2.0
Django==4.0.8
django-celery-beat==2.4.0
django-dotenv==1.4.2
django-environ==0.9.0
django-timezone-field==5.0
djangorestframework==3.14.0
djangorestframework-simplejwt==5.2.2
exceptiongroup==1.1.0
execnet==1.9.0
flower==1.2.0
gunicorn==20.1.0
humanize==4.4.0
idna==3.4
iniconfig==1.1.1
kombu==5.2.4
mypy-extensions==0.4.3
packaging==22.0
pathspec==0.10.3
platformdirs==2.6.0
pluggy==1.0.0
prometheus-client==0.15.0
prompt-toolkit==3.0.36
psycopg2-binary==2.9.5
PyJWT==2.6.0
pytest==7.2.0
pytest-django==4.5.2
pytest-django-dotenv==0.1.2
pytest-xdist==3.1.0
python-crontab==2.7.1
python-dateutil==2.8.2
pytz==2022.7
redis==4.4.0
requests==2.28.1
six==1.16.0
soupsieve==2.3.2.post1
sqlparse==0.4.3
tomli==2.0.1
tornado==6.2
typing_extensions==4.4.0
tzdata==2022.7
urllib3==1.26.13
vine==5.0.0
wcwidth==0.2.5
pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = modu_property.settings
python_files = test*.py
django_find_project = true
env_path = ./.env.test
log_cli = 1
log_cli_level = DEBUG
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
log_cli_date_format=%Y-%m-%d %H:%M:%S
settings.py
import datetime
import json
from pathlib import Path
import environ
import os
from celery.schedules import crontab
env = environ.Env(
# set casting, default value
# DEBUG=(bool, False)
)
# Set the project base directory
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def set_logging():
return {
"version": 1,
"disable_existing_loggers": False,
"filters": {
"require_debug_false": {
"()": "django.utils.log.RequireDebugFalse",
},
"require_debug_true": {
"()": "django.utils.log.RequireDebugTrue",
},
},
"formatters": {
"django.server": {
"()": "django.utils.log.ServerFormatter",
"format": "{asctime} {filename}:{funcName}:{lineno} [{levelname}] {message}",
"style": "{",
},
},
"handlers": {
"django.server": {
"level": env("LOG_LEVEL"),
"class": "logging.StreamHandler",
"formatter": "django.server",
},
"file": {
"level": env("LOG_LEVEL"),
"filters": ["require_debug_true"],
"class": "logging.handlers.RotatingFileHandler",
"filename": f"{BASE_DIR}/modu_property.log",
"maxBytes": 1024 * 1024 * 5, # 5 MB
"backupCount": 5,
"formatter": "django.server",
},
},
"loggers": {
"django.server": {
"handlers": ["django.server"],
"level": env("LOG_LEVEL"),
"propagete": True,
},
"file": {
"handlers": ["file"],
"level": env("LOG_LEVEL"),
"propagete": True,
},
},
}
# FROM .env.* file
SERVER_ENV = os.environ.get("SERVER_ENV")
if SERVER_ENV == "dev":
environ.Env.read_env(os.path.join(BASE_DIR, ".env.dev"))
elif SERVER_ENV == "stage":
environ.Env.read_env(os.path.join(BASE_DIR, ".env.stage"))
# elif SERVER_ENV == "prod":
# environ.Env.read_env(os.path.join(BASE_DIR, ".env.prod"))
elif SERVER_ENV == "test":
environ.Env.read_env(os.path.join(BASE_DIR, ".env.test"))
else:
environ.Env.read_env(os.path.join(BASE_DIR, ".env.local"))
LOGGING = set_logging()
# False if not in os.environ because of casting above
DEBUG = env("DEBUG")
# Raises Django's ImproperlyConfigured
# exception if SECRET_KEY not in os.environ
SECRET_KEY = env("SECRET_KEY")
CELERY_BROKER_URL = env("CELERY_BROKER_URL")
ENGINE = env("DB_ENGINE")
NAME = env("DB_NAME")
USER = env("USER")
PASSWORD = env("PASSWORD")
HOST = env("HOST")
PORT = env("PORT")
NAVER_NEW_API_CLIENT_ID = env("NAVER_NEW_API_CLIENT_ID")
NAVER_NEW_API_CLIENT_SECRET = env("NAVER_NEW_API_CLIENT_SECRET")
DJANGO_ALLOWED_HOSTS = env("DJANGO_ALLOWED_HOSTS", default="").split(" ")
ALLOWED_HOSTS = DJANGO_ALLOWED_HOSTS
POSTGRES_USER = env("POSTGRES_USER", default="")
POSTGRES_PASSWORD = env("POSTGRES_PASSWORD", default="")
POSTGRES_DB = env("POSTGRES_DB", default="")
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"app",
"accounts",
"modu_property",
"django_celery_beat",
# "rest_framework_simplejwt",
]
# TODO : rest_framework_simplejwt 설정 필요 없으면 제거
# REST_FRAMEWORK = {
# "DEFAULT_AUTHENTICATION_CLASSES": (
# "rest_framework_simplejwt.authentication.JWTAuthentication",
# )
# }
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
# "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "modu_property.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "modu_property.wsgi.application"
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": ENGINE,
"NAME": NAME,
"USER": USER,
"PASSWORD": PASSWORD,
"HOST": HOST,
"PORT": PORT,
},
}
# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/
STATIC_URL = "static/"
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": datetime.timedelta(hours=6),
"REFRESH_TOKEN_LIFETIME": datetime.timedelta(days=14),
"SIGNING_KEY": SECRET_KEY,
"ALGORITHM": "HS256",
"AUTH_HEADER_TYPES": ("JWT",),
}
AUTH_USER_MODEL = "accounts.User"
# Celery Configuration Options
CELERY_TIMEZONE = "UTC"
CELERY_TASK_TRACK_STARTED = True
CELERY_TASK_TIME_LIMIT = 30 * 60
CELERY_BROKER_URL = CELERY_BROKER_URL
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_BEAT_SCHEDULE = {
"collect_property_news_every_5_minutes": {
"task": "modu_property.tasks.collect_property_news_task",
"schedule": crontab(minute="*/5"),
"args": json.dumps(
{
"days": 100,
}
),
},
}
I ran pytest
after activating venv
and the build was successful:
jobs:
test_project:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install Dependencies
run: |
pip install --upgrade pip
python3.8 -m venv env
source env/bin/activate
pip install -r requirements.txt
pytest .